From 2cc73e62ff82c740c90af4ef02077816cd0126d3 Mon Sep 17 00:00:00 2001 From: Ross Date: Sat, 18 Dec 2021 09:14:41 +0000 Subject: [PATCH] . --- anatomy/urls.py | 2 +- anatomy/views.py | 236 +++++++++--------- generic/views.py | 126 ++++++++++ physics/models.py | 54 ++-- .../physics/ciduseranswer_detail.html | 11 +- physics/views.py | 25 +- 6 files changed, 296 insertions(+), 158 deletions(-) diff --git a/anatomy/urls.py b/anatomy/urls.py index fe33e029..08901549 100644 --- a/anatomy/urls.py +++ b/anatomy/urls.py @@ -78,7 +78,7 @@ urlpatterns = [ ), path( "exam//scores", - views.exam_scores_cid, + views.AnatomyExamViews.exam_scores_cid, # cache_page(60 * 1)(views.exam_scores_cid), name="exam_scores_cid", ), diff --git a/anatomy/views.py b/anatomy/views.py index c455c4f5..26ea6c9b 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -475,124 +475,124 @@ def exam_scores_refresh(request, pk): }, ) -@login_required -def exam_scores_cid(request, 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() - - cids = ( - 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, - "anatomy/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, - }, - ) +#@login_required +#def exam_scores_cid(request, 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() +# +# cids = ( +# 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, +# "anatomy/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, +# }, +# ) def exam_scores_cid_user(request, pk, sk, passcode): diff --git a/generic/views.py b/generic/views.py index 06a73196..290d2f64 100644 --- a/generic/views.py +++ b/generic/views.py @@ -1,3 +1,5 @@ +from collections import defaultdict +import statistics from dal import autocomplete from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -54,6 +56,7 @@ from datetime import datetime import os import string import random +import plotly.express as px # from rad.views import get_question_and_content_type @@ -833,6 +836,129 @@ class ExamViews(View, LoginRequiredMixin): 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: def __init__(self, app_name, QuestionObject, CidUserAnswerObject, ExamObject): diff --git a/physics/models.py b/physics/models.py index 4b852dbe..0ffe2aa9 100644 --- a/physics/models.py +++ b/physics/models.py @@ -16,12 +16,14 @@ from generic.models import CidUser, ExamBase, QuestionNote import reversion + class Category(models.Model): category = models.CharField(max_length=200) def __str__(self): return self.category + class Question(models.Model): stem = models.TextField( blank=False, @@ -34,9 +36,7 @@ class Question(models.Model): blank=False, help_text="The question text", ) - a_answer = models.BooleanField( - help_text=answer_help_text, default=True - ) + a_answer = models.BooleanField(help_text=answer_help_text, default=True) a_feedback = models.TextField( blank=True, help_text="Feedback for answer", @@ -46,9 +46,7 @@ class Question(models.Model): blank=False, help_text="The question text", ) - b_answer = models.BooleanField( - help_text=answer_help_text, default=True - ) + b_answer = models.BooleanField(help_text=answer_help_text, default=True) b_feedback = models.TextField( blank=True, help_text="Feedback for answer", @@ -58,9 +56,7 @@ class Question(models.Model): blank=False, help_text="The question text", ) - c_answer = models.BooleanField( - help_text=answer_help_text, default=True - ) + c_answer = models.BooleanField(help_text=answer_help_text, default=True) c_feedback = models.TextField( blank=True, help_text="Feedback for answer", @@ -70,9 +66,7 @@ class Question(models.Model): blank=False, help_text="The question text", ) - d_answer = models.BooleanField( - help_text=answer_help_text, default=True - ) + d_answer = models.BooleanField(help_text=answer_help_text, default=True) d_feedback = models.TextField( blank=True, help_text="Feedback for answer", @@ -82,9 +76,7 @@ class Question(models.Model): blank=False, help_text="The question text", ) - e_answer = models.BooleanField( - help_text=answer_help_text, default=True - ) + e_answer = models.BooleanField(help_text=answer_help_text, default=True) e_feedback = models.TextField( blank=True, help_text="Feedback for answer", @@ -124,7 +116,13 @@ class Question(models.Model): return reverse("physics:question_detail", kwargs={"pk": self.pk}) 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): return str(self.get_answers()).lower() @@ -136,10 +134,8 @@ class Question(models.Model): @reversion.register class Exam(ExamBase): app_name = "physics" - - exam_questions = SortedManyToManyField( - Question, related_name="exams", blank="true" - ) + + exam_questions = SortedManyToManyField(Question, related_name="exams", blank="true") time_limit = models.IntegerField( help_text="Exam time limit (in seconds)", @@ -149,12 +145,16 @@ class Exam(ExamBase): recreate_json = None - author = models.ManyToManyField(settings.AUTH_USER_MODEL, - blank=True, - help_text='Author of exam', - related_name="physics_exam_author") + author = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + 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): return reverse("physics:exam_start", kwargs={"pk": self.pk}) @@ -189,7 +189,7 @@ class CidUserAnswer(models.Model): def get_answers(self): return (self.a, self.b, self.c, self.d, self.e) - def get_answer_scores(self): + def get_answer_score(self): q = self.question if self.a == q.a_answer: @@ -217,4 +217,4 @@ class CidUserAnswer(models.Model): else: score_e = 0 - return (score_a, score_b, score_c, score_d, score_e) \ No newline at end of file + return (score_a, score_b, score_c, score_d, score_e) diff --git a/physics/templates/physics/ciduseranswer_detail.html b/physics/templates/physics/ciduseranswer_detail.html index 7b09fcc9..173f034a 100644 --- a/physics/templates/physics/ciduseranswer_detail.html +++ b/physics/templates/physics/ciduseranswer_detail.html @@ -1,11 +1,10 @@ - {% extends 'physics/base.html' %} {% block content %} -CID: {{ciduseranswer.cid}}
-Exam: {{ciduseranswer.exam}}
-{{ciduseranswer.question}}
-Answers:
+CID: {{ciduseranswer.cid}}
+Exam: {{ciduseranswer.exam}}
+{{ciduseranswer.question}}
+Answers:
  1. {{ciduseranswer.a}}
  2. {{ciduseranswer.b}}
  3. @@ -13,6 +12,6 @@ Answers:
  4. {{ciduseranswer.d}}
  5. {{ciduseranswer.e}}
-Score: {{ciduseranswer.get_answer_scores}}
+Score: {{ciduseranswer.get_answer_score}}
Delete answer {% endblock content %} \ No newline at end of file diff --git a/physics/views.py b/physics/views.py index 19b9dbf3..a8bbf873 100644 --- a/physics/views.py +++ b/physics/views.py @@ -91,6 +91,9 @@ def active_exams(request): def exam_scores_cid(request, 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() cids = ( @@ -117,13 +120,21 @@ def exam_scores_cid(request, pk): 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,0,0,0,0)) + user_answers_marks[cid].append((0, 0, 0, 0, 0)) user_answers[cid].append(("", "", "", "", "")) - by_question[q].append((("", 0),("", 0),("", 0),("", 0),("", 0),)) + by_question[q].append( + ( + ("", 0), + ("", 0), + ("", 0), + ("", 0), + ("", 0), + ) + ) continue ans = s.get_answers() - answer_scores = s.get_answer_scores() + answer_scores = s.get_answer_score() user_answers[cid].append(ans) 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() - answer_scores = user_answer.get_answer_scores() + answer_scores = user_answer.get_answer_score() correct_answers = q.get_answers() @@ -235,7 +246,7 @@ def exam_scores_cid_user(request, pk, sk, passcode): { "exam": exam, "cid": cid, - "passcode" : passcode, + "passcode": passcode, "questions": questions, "answers": answers, "answers_marks": answers_marks, @@ -452,7 +463,9 @@ def loadJsonAnswer(answer): 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)