diff --git a/anatomy/models.py b/anatomy/models.py index 3d629556..ce552862 100644 --- a/anatomy/models.py +++ b/anatomy/models.py @@ -158,6 +158,16 @@ class AnatomyQuestion(models.Model): return reverse("anatomy:question_detail", kwargs={"pk": self.pk}) def get_primary_answer(self): + ans = self.answers.filter( + proposed=False, status=Answer.MarkOptions.CORRECT + ).first() + # ans = self.answers.first() + + if ans is None: + return "None yet..." + else: + return ans.answer + if ( self.answers.filter( proposed=False, status=Answer.MarkOptions.CORRECT @@ -434,7 +444,7 @@ class CidUserAnswer(models.Model): max_length=1, choices=Answer.MarkOptions.choices, default=Answer.MarkOptions.UNMARKED, - blank=True + blank=True, ) def __str__(self): diff --git a/anatomy/templates/anatomy/exam_overview.html b/anatomy/templates/anatomy/exam_overview.html index 35c2e5ad..640ad253 100644 --- a/anatomy/templates/anatomy/exam_overview.html +++ b/anatomy/templates/anatomy/exam_overview.html @@ -33,7 +33,7 @@ {% endif %}
    - {% for question in questions.all %} + {% for question in questions %}
  1. diff --git a/anatomy/templates/anatomy/exam_scores_new.html b/anatomy/templates/anatomy/exam_scores_new.html index 2ad3b17a..2f2c2684 100644 --- a/anatomy/templates/anatomy/exam_scores_new.html +++ b/anatomy/templates/anatomy/exam_scores_new.html @@ -57,14 +57,14 @@ {% for question in questions %} Question {{forloop.counter}} - {% if question.normal %} - [N] - {% else %} - [A] - {% endif %} - {% for cid in cids %} + {% comment %} {% for cid in cids %} {{ans_by_question|get_item:question|get_item:cid}} + {% endfor %} {% endcomment %} + {% for cid in cids %} + {% with by_question|get_item:question|get_item:cid as ans_score %} + {{ans_score.0}} + {% endwith %} {% endfor %} {% endfor %} diff --git a/anatomy/views.py b/anatomy/views.py index 62aef635..61b0e756 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -1004,7 +1004,7 @@ def question_save_annotation(request, pk): GenericExamViews = ExamViews( - Exam, AnatomyQuestion, CidUserAnswer, "anatomy", "anatomy", loadJsonAnswer + Exam, AnatomyQuestion, Answer, CidUserAnswer, "anatomy", "anatomy", loadJsonAnswer ) diff --git a/generic/views.py b/generic/views.py index 63cf609d..5379836d 100644 --- a/generic/views.py +++ b/generic/views.py @@ -59,6 +59,7 @@ import random import plotly.express as px # from rad.views import get_question_and_content_type +from django.db.models import Prefetch def normaliseRapidsScore(score): @@ -240,10 +241,18 @@ def generic_exam_json_edit(request): class ExamViews(View, LoginRequiredMixin): def __init__( - self, exam, question, cid_user_answer, app, question_type, loadJsonAnswer + self, + exam, + question, + answer, + cid_user_answer, + app, + question_type, + loadJsonAnswer, ): self.Exam = exam self.Question = question + self.Answer = answer self.CidUserAnswer = cid_user_answer self.app_name = app self.question_type = question_type @@ -414,7 +423,7 @@ class ExamViews(View, LoginRequiredMixin): # print(Exam.objects.all()) # exams = Exam.objects.all() # print("test", Exam.objects.all().get(id=pk)) - exam = get_object_or_404(self.Exam, pk=pk) + exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=pk) if request.user not in exam.author.all(): if not self.check_user_access(request.user, pk): @@ -428,7 +437,38 @@ class ExamViews(View, LoginRequiredMixin): if can_edit: notes = self.exam_notes(request, pk) - questions = exam.exam_questions.all() + if self.app_name == "anatomy": + questions = ( + exam.exam_questions.select_related( + "modality", + "structure", + "body_part", + "region", + "examination", + "question_type", + ).all() + # .prefetch_related( + # Prefetch( + # "answers", + # queryset=self.Answer.objects.filter( + # proposed=False, status=self.Answer.MarkOptions.CORRECT + # ), + # ) + # ) + ) + elif self.app_name == "rapids": + questions = ( + exam.exam_questions.select_related() + .all() + .prefetch_related("images", "abnormality", "region", "examination") + ) + elif self.app_name == "physics": + questions = ( + exam.exam_questions.select_related("category").all() + # .prefetch_related("images", "abnormality", "region", "examination") + ) + else: + questions = exam.exam_questions.select_related().all() question_number = len(questions) @@ -868,13 +908,15 @@ class ExamViews(View, LoginRequiredMixin): user_answers = defaultdict(list) user_names = {} - score_by_question = defaultdict(dict) - ans_by_question = defaultdict(dict) + by_question = defaultdict(dict) unmarked = set() questions = exam.exam_questions.all() - cid_user_answers = self.CidUserAnswer.objects.filter(question__in=questions, exam__id=pk) + # We could prefect the CidUserAnswers.answers here (if we didn't cache them) + cid_user_answers = self.CidUserAnswer.objects.select_related("question").filter( + question__in=questions, exam__id=pk + ) cids = set() @@ -905,15 +947,25 @@ class ExamViews(View, LoginRequiredMixin): user_answers_marks[cid].append(answer_score) user_answers_and_marks[cid].append((ans, answer_score)) - score_by_question[q][cid] = answer_score - ans_by_question[q][cid] = ans + if self.app_name in ("rapids", "anatomy", "sbas"): + by_question[q][cid] = (ans, answer_score) + else: + zipped_ans_scores = zip(ans, answer_score) + by_question[q][cid] = zipped_ans_scores user_scores = {} user_scores_normalised = {} for user in user_answers_marks: - user_scores[user] = sum( - [i for i in user_answers_marks[user] if i != "unmarked"] - ) + + if self.app_name in ("rapids", "anatomy", "sbas"): + user_scores[user] = sum( + [i for i in user_answers_marks[user] if i != "unmarked"] + ) + else: + user_scores[user] = sum( + [sum(i) for i in user_answers_marks[user] if i != "unmarked"] + ) + if self.app_name == "rapids": user_scores_normalised[user] = normaliseRapidsScore( sum([i for i in user_answers_marks[user] if i != "unmarked"]) @@ -923,7 +975,12 @@ class ExamViews(View, LoginRequiredMixin): user_scores_list = list(user_scores.values()) - max_score = len(questions) * 2 + if self.app_name in ("rapids", "anatomy"): + max_score = len(questions) * 2 + elif self.app_name == "physics": + max_score = len(questions) * 5 + else: + max_score = len(questions) if len(user_scores_list) < 1: mean = 0 @@ -962,7 +1019,6 @@ class ExamViews(View, LoginRequiredMixin): exam.stats_graph = fig_html exam.save() - return render( request, f"{self.app_name}/exam_scores_new.html", @@ -971,8 +1027,7 @@ class ExamViews(View, LoginRequiredMixin): "exam": exam, "unmarked": unmarked, "questions": questions, - "score_by_question": score_by_question, - "ans_by_question": ans_by_question, + "by_question": by_question, "user_answers": dict(user_answers), "user_answers_marks": dict(user_answers_marks), "user_scores": user_scores, diff --git a/longs/views.py b/longs/views.py index 7bcf2b31..22ed6395 100755 --- a/longs/views.py +++ b/longs/views.py @@ -1170,7 +1170,7 @@ def long_series_order_upload_filename(request, pk): return redirect("longs:long_series_detail", pk=pk) -GenericExamViews = ExamViews(Exam, Long, CidUserAnswer, "longs", "long", loadJsonAnswer) +GenericExamViews = ExamViews(Exam, Long, None, CidUserAnswer, "longs", "long", loadJsonAnswer) class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): diff --git a/physics/templates/physics/exam_scores_new.html b/physics/templates/physics/exam_scores_new.html new file mode 100644 index 00000000..4584043f --- /dev/null +++ b/physics/templates/physics/exam_scores_new.html @@ -0,0 +1,73 @@ +{% extends 'physics/exams.html' %} + +{% block content %} +
    {{plot|safe}}
    +
    +

    {{ exam.name }}

    + +
    +
    +

    Stats

    + Candidates: {{cids|length}}
    + Max score: {{max_score}}
    + Mean: {{mean}}, Median {{median}}, Mode {{mode}} + +
    +
    + + + + + {% comment %} {% endcomment %} + + {% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %} + {% for cid in cids %} + + + + {% comment %} {% endcomment %} + + {% endfor %} +
    Candidate IDScoreNormalised Score
    {{cid}}{{user_scores|get_item:cid}}{{user_scores_normalised|get_item:user}}
    +
    +
    +

    Answers as a table

    + + + + + {% for cid in cids %} + {% comment %} {% endcomment %} + + {% endfor %} + + + + {% for question in questions %} + + + {% for cid in cids %} + + {% endfor %} + + {% endfor %} + + + {% for cid in cids %} + + {% endfor %} + +
    Candidate{{cid}}{{cid}}
    Question {{forloop.counter}} + +
      + {% with by_question|get_item:question|get_item:cid as ans_score %} + {% for ans, score in ans_score %} +
    1. {{ans}}
    2. + {% endfor %} + {% endwith %} +
    +
    Score:{{user_scores|get_item:cid}}
    + +
    +{% endblock %} + diff --git a/physics/urls.py b/physics/urls.py index 7d905204..71e61d8b 100644 --- a/physics/urls.py +++ b/physics/urls.py @@ -26,7 +26,7 @@ urlpatterns = [ name="exam_finish", ), path("exam//", views.GenericExamViews.exam_overview, name="exam_overview"), - path("exam//scores", views.exam_scores_cid, name="exam_scores_cid"), + path("exam//scores", views.GenericExamViews.exam_scores_cid, name="exam_scores_cid"), path( "exam//scores///", views.exam_scores_cid_user, diff --git a/physics/views.py b/physics/views.py index 073f0fe2..1baa5399 100644 --- a/physics/views.py +++ b/physics/views.py @@ -464,7 +464,7 @@ def loadJsonAnswer(answer): GenericExamViews = ExamViews( - Exam, Question, CidUserAnswer, "physics", "physics", loadJsonAnswer + Exam, Question, None, CidUserAnswer, "physics", "physics", loadJsonAnswer ) GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) diff --git a/rapids/models.py b/rapids/models.py index 0933ad00..f6f8b597 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -658,7 +658,7 @@ class CidUserAnswer(models.Model): max_length=1, choices=Answer.MarkOptions.choices, default=Answer.MarkOptions.UNMARKED, - blank=True + blank=True, ) def __str__(self): @@ -700,6 +700,7 @@ class CidUserAnswer(models.Model): def get_answer_score(self, cached=True): if cached and self.score: + print("CACHED") if self.score == Answer.MarkOptions.CORRECT: mark = 2 elif self.score == Answer.MarkOptions.HALF_MARK: @@ -708,6 +709,7 @@ class CidUserAnswer(models.Model): mark = 0 return mark + print("NOT CACHED") q = self.question @@ -731,6 +733,9 @@ class CidUserAnswer(models.Model): mark = "unmarked" if marked_ans is not None: + self.score = marked_ans.status + self.save() + if marked_ans.status == Answer.MarkOptions.CORRECT: mark = 2 elif marked_ans.status == Answer.MarkOptions.HALF_MARK: diff --git a/rapids/templates/rapids/exam_scores_new.html b/rapids/templates/rapids/exam_scores_new.html index bd9fd93b..4eae3453 100644 --- a/rapids/templates/rapids/exam_scores_new.html +++ b/rapids/templates/rapids/exam_scores_new.html @@ -31,11 +31,11 @@ Score Normalised Score - {% for user, value in user_answers_marks.items %} + {% for cid in cids %} - {{user}} - {{user_scores|get_item:user}} - {{user_scores_normalised|get_item:user}} + {{cid}} + {{user_scores|get_item:cid}} + {{user_scores_normalised|get_item:cid}} {% endfor %} @@ -63,7 +63,10 @@ {% endif %} {% for cid in cids %} - {{ans_by_question|get_item:question|get_item:cid}} + {% comment %} {{ans_by_question|get_item:question|get_item:cid}} {% endcomment %} + {% with by_question|get_item:question|get_item:cid as ans_score %} + {{ans_score.0}} + {% endwith %} {% endfor %} {% endfor %} diff --git a/rapids/views.py b/rapids/views.py index e03f069c..812f4018 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -1048,7 +1048,7 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView): GenericExamViews = ExamViews( - Exam, Rapid, CidUserAnswer, "rapids", "rapid", loadJsonAnswer + Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid", loadJsonAnswer ) diff --git a/sbas/models.py b/sbas/models.py index 9f9ef893..e931f853 100644 --- a/sbas/models.py +++ b/sbas/models.py @@ -196,13 +196,16 @@ class CidUserAnswer(models.Model): def __str__(self): return "{}/{}/{}".format(self.cid, self.exam, self.question.pk) + def get_answer_string(self): + return self.answer + def get_answer(self): return self.answer def get_correct(self): return self.answer == self.question.best_answer - def get_score(self): + def get_answer_score(self): if self.get_correct(): return 1 return 0 diff --git a/sbas/templates/sbas/exam_scores_new.html b/sbas/templates/sbas/exam_scores_new.html new file mode 100644 index 00000000..c2116a58 --- /dev/null +++ b/sbas/templates/sbas/exam_scores_new.html @@ -0,0 +1,70 @@ +{% extends 'sbas/exams.html' %} + +{% block content %} +
    {{plot|safe}}
    +
    +

    {{ exam.name }}

    + +
    +
    +

    Stats

    + Candidates: {{cids|length}}
    + Max score: {{max_score}}
    + Mean: {{mean}}, Median {{median}}, Mode {{mode}} + +
    +
    + + + + + {% comment %} {% endcomment %} + + {% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %} + {% for cid in cids %} + + + + {% comment %} {% endcomment %} + + {% endfor %} +
    Candidate IDScoreNormalised Score
    {{cid}}{{user_scores|get_item:cid}}{{user_scores_normalised|get_item:user}}
    +
    +
    +

    Answers as a table

    + + + + + {% for cid in cids %} + {% comment %} {% endcomment %} + + {% endfor %} + + + + {% for question in questions %} + + + {% comment %} {% for cid in cids %} + + {% endfor %} {% endcomment %} + {% for cid in cids %} + {% with by_question|get_item:question|get_item:cid as ans_score %} + + {% endwith %} + {% endfor %} + + {% endfor %} + + + {% for cid in cids %} + + {% endfor %} + +
    Candidate{{cid}}{{cid}}
    Question {{forloop.counter}} + {{ans_by_question|get_item:question|get_item:cid}}{{ans_score.0}}
    Score:{{user_scores|get_item:cid}}
    + +
    +{% endblock %} + diff --git a/sbas/urls.py b/sbas/urls.py index fcad21e6..0e015375 100644 --- a/sbas/urls.py +++ b/sbas/urls.py @@ -28,7 +28,7 @@ urlpatterns = [ path("exam//", views.GenericExamViews.exam_overview, name="exam_overview"), path( "exam//scores", - cache_page(60 * 1)(views.exam_scores_cid), + views.GenericExamViews.exam_scores_cid, name="exam_scores_cid", ), path( diff --git a/sbas/views.py b/sbas/views.py index bc15c3e7..1c461eff 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -383,7 +383,7 @@ def loadJsonAnswer(answer): GenericExamViews = ExamViews( - Exam, Question, CidUserAnswer, "sbas", "sbas", loadJsonAnswer + Exam, Question, None, CidUserAnswer, "sbas", "sbas", loadJsonAnswer )