.
This commit is contained in:
+11
-1
@@ -158,6 +158,16 @@ class AnatomyQuestion(models.Model):
|
|||||||
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
def get_primary_answer(self):
|
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 (
|
if (
|
||||||
self.answers.filter(
|
self.answers.filter(
|
||||||
proposed=False, status=Answer.MarkOptions.CORRECT
|
proposed=False, status=Answer.MarkOptions.CORRECT
|
||||||
@@ -434,7 +444,7 @@ class CidUserAnswer(models.Model):
|
|||||||
max_length=1,
|
max_length=1,
|
||||||
choices=Answer.MarkOptions.choices,
|
choices=Answer.MarkOptions.choices,
|
||||||
default=Answer.MarkOptions.UNMARKED,
|
default=Answer.MarkOptions.UNMARKED,
|
||||||
blank=True
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ol id="full-question-list" class="sortable">
|
<ol id="full-question-list" class="sortable">
|
||||||
{% for question in questions.all %}
|
{% for question in questions %}
|
||||||
|
|
||||||
<li data-question_pk={{question.pk}}>
|
<li data-question_pk={{question.pk}}>
|
||||||
<span class="flex-col">
|
<span class="flex-col">
|
||||||
|
|||||||
@@ -57,14 +57,14 @@
|
|||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
|
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
|
||||||
{% if question.normal %}
|
|
||||||
[N]
|
|
||||||
{% else %}
|
|
||||||
[A]
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
{% for cid in cids %}
|
{% comment %} {% for cid in cids %}
|
||||||
<td class="anatomy-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
|
<td class="anatomy-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
|
||||||
|
{% endfor %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
{% with by_question|get_item:question|get_item:cid as ans_score %}
|
||||||
|
<td class="anatomy-ans user-answer-score-{{ans_score.1}}" title="answer score: {{ans_score.1}}">{{ans_score.0}}</td>
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+1
-1
@@ -1004,7 +1004,7 @@ def question_save_annotation(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(
|
GenericExamViews = ExamViews(
|
||||||
Exam, AnatomyQuestion, CidUserAnswer, "anatomy", "anatomy", loadJsonAnswer
|
Exam, AnatomyQuestion, Answer, CidUserAnswer, "anatomy", "anatomy", loadJsonAnswer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+66
-11
@@ -59,6 +59,7 @@ import random
|
|||||||
import plotly.express as px
|
import plotly.express as px
|
||||||
|
|
||||||
# from rad.views import get_question_and_content_type
|
# from rad.views import get_question_and_content_type
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
|
||||||
|
|
||||||
def normaliseRapidsScore(score):
|
def normaliseRapidsScore(score):
|
||||||
@@ -240,10 +241,18 @@ def generic_exam_json_edit(request):
|
|||||||
|
|
||||||
class ExamViews(View, LoginRequiredMixin):
|
class ExamViews(View, LoginRequiredMixin):
|
||||||
def __init__(
|
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.Exam = exam
|
||||||
self.Question = question
|
self.Question = question
|
||||||
|
self.Answer = answer
|
||||||
self.CidUserAnswer = cid_user_answer
|
self.CidUserAnswer = cid_user_answer
|
||||||
self.app_name = app
|
self.app_name = app
|
||||||
self.question_type = question_type
|
self.question_type = question_type
|
||||||
@@ -414,7 +423,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
# print(Exam.objects.all())
|
# print(Exam.objects.all())
|
||||||
# exams = Exam.objects.all()
|
# exams = Exam.objects.all()
|
||||||
# print("test", Exam.objects.all().get(id=pk))
|
# 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 request.user not in exam.author.all():
|
||||||
if not self.check_user_access(request.user, pk):
|
if not self.check_user_access(request.user, pk):
|
||||||
@@ -428,7 +437,38 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if can_edit:
|
if can_edit:
|
||||||
notes = self.exam_notes(request, pk)
|
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)
|
question_number = len(questions)
|
||||||
|
|
||||||
@@ -868,13 +908,15 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
user_answers = defaultdict(list)
|
user_answers = defaultdict(list)
|
||||||
user_names = {}
|
user_names = {}
|
||||||
|
|
||||||
score_by_question = defaultdict(dict)
|
by_question = defaultdict(dict)
|
||||||
ans_by_question = defaultdict(dict)
|
|
||||||
unmarked = set()
|
unmarked = set()
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
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()
|
cids = set()
|
||||||
|
|
||||||
@@ -905,15 +947,25 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
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))
|
||||||
|
|
||||||
score_by_question[q][cid] = answer_score
|
if self.app_name in ("rapids", "anatomy", "sbas"):
|
||||||
ans_by_question[q][cid] = ans
|
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 = {}
|
||||||
user_scores_normalised = {}
|
user_scores_normalised = {}
|
||||||
for user in user_answers_marks:
|
for user in user_answers_marks:
|
||||||
|
|
||||||
|
if self.app_name in ("rapids", "anatomy", "sbas"):
|
||||||
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"]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
user_scores[user] = sum(
|
||||||
|
[sum(i) for i in user_answers_marks[user] if i != "unmarked"]
|
||||||
|
)
|
||||||
|
|
||||||
if self.app_name == "rapids":
|
if self.app_name == "rapids":
|
||||||
user_scores_normalised[user] = normaliseRapidsScore(
|
user_scores_normalised[user] = normaliseRapidsScore(
|
||||||
sum([i for i in user_answers_marks[user] if i != "unmarked"])
|
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())
|
user_scores_list = list(user_scores.values())
|
||||||
|
|
||||||
|
if self.app_name in ("rapids", "anatomy"):
|
||||||
max_score = len(questions) * 2
|
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:
|
if len(user_scores_list) < 1:
|
||||||
mean = 0
|
mean = 0
|
||||||
@@ -962,7 +1019,6 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
exam.stats_graph = fig_html
|
exam.stats_graph = fig_html
|
||||||
exam.save()
|
exam.save()
|
||||||
|
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
f"{self.app_name}/exam_scores_new.html",
|
f"{self.app_name}/exam_scores_new.html",
|
||||||
@@ -971,8 +1027,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
"exam": exam,
|
"exam": exam,
|
||||||
"unmarked": unmarked,
|
"unmarked": unmarked,
|
||||||
"questions": questions,
|
"questions": questions,
|
||||||
"score_by_question": score_by_question,
|
"by_question": by_question,
|
||||||
"ans_by_question": ans_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,
|
||||||
|
|||||||
+1
-1
@@ -1170,7 +1170,7 @@ def long_series_order_upload_filename(request, pk):
|
|||||||
return redirect("longs:long_series_detail", pk=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):
|
class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{% extends 'physics/exams.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="stats-plot">{{plot|safe}}</div>
|
||||||
|
<div class="physics">
|
||||||
|
<h2>{{ exam.name }}</h2>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="stats-block">
|
||||||
|
<h3>Stats</h3>
|
||||||
|
Candidates: {{cids|length}}<br />
|
||||||
|
Max score: {{max_score}}<br />
|
||||||
|
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm cid-score-table">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate ID</th>
|
||||||
|
<th>Score</th>
|
||||||
|
{% comment %} <th>Normalised Score</th> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% comment %} <td>{{user_scores_normalised|get_item:user}}</td> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Answers as a table</h3>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm col-sm">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate</th>
|
||||||
|
{% for cid in cids %}
|
||||||
|
{% comment %} <th><a href="{% url 'physics:exam_scores_cid_user' exam.pk cid %}">{{cid}}</a></th> {% endcomment %}
|
||||||
|
<th><a href="">{{cid}}</a></th>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for question in questions %}
|
||||||
|
<tr>
|
||||||
|
<td>Question {{forloop.counter}}
|
||||||
|
</td>
|
||||||
|
{% for cid in cids %}
|
||||||
|
<td>
|
||||||
|
<ol class="physics-ans" style="list-style-type: lower-alpha;">
|
||||||
|
{% with by_question|get_item:question|get_item:cid as ans_score %}
|
||||||
|
{% for ans, score in ans_score %}
|
||||||
|
<li class="user-answer-score-{{score}}" title="answer score: {{score}}">{{ans}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
</ol>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td>Score:</td>
|
||||||
|
{% for cid in cids %}
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
+1
-1
@@ -26,7 +26,7 @@ urlpatterns = [
|
|||||||
name="exam_finish",
|
name="exam_finish",
|
||||||
),
|
),
|
||||||
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
||||||
path("exam/<int:pk>/scores", views.exam_scores_cid, name="exam_scores_cid"),
|
path("exam/<int:pk>/scores", views.GenericExamViews.exam_scores_cid, name="exam_scores_cid"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
||||||
views.exam_scores_cid_user,
|
views.exam_scores_cid_user,
|
||||||
|
|||||||
+1
-1
@@ -464,7 +464,7 @@ def loadJsonAnswer(answer):
|
|||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(
|
GenericExamViews = ExamViews(
|
||||||
Exam, Question, CidUserAnswer, "physics", "physics", loadJsonAnswer
|
Exam, Question, None, CidUserAnswer, "physics", "physics", loadJsonAnswer
|
||||||
)
|
)
|
||||||
|
|
||||||
GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam)
|
GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam)
|
||||||
|
|||||||
+6
-1
@@ -658,7 +658,7 @@ class CidUserAnswer(models.Model):
|
|||||||
max_length=1,
|
max_length=1,
|
||||||
choices=Answer.MarkOptions.choices,
|
choices=Answer.MarkOptions.choices,
|
||||||
default=Answer.MarkOptions.UNMARKED,
|
default=Answer.MarkOptions.UNMARKED,
|
||||||
blank=True
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -700,6 +700,7 @@ class CidUserAnswer(models.Model):
|
|||||||
|
|
||||||
def get_answer_score(self, cached=True):
|
def get_answer_score(self, cached=True):
|
||||||
if cached and self.score:
|
if cached and self.score:
|
||||||
|
print("CACHED")
|
||||||
if self.score == Answer.MarkOptions.CORRECT:
|
if self.score == Answer.MarkOptions.CORRECT:
|
||||||
mark = 2
|
mark = 2
|
||||||
elif self.score == Answer.MarkOptions.HALF_MARK:
|
elif self.score == Answer.MarkOptions.HALF_MARK:
|
||||||
@@ -708,6 +709,7 @@ class CidUserAnswer(models.Model):
|
|||||||
mark = 0
|
mark = 0
|
||||||
|
|
||||||
return mark
|
return mark
|
||||||
|
print("NOT CACHED")
|
||||||
|
|
||||||
q = self.question
|
q = self.question
|
||||||
|
|
||||||
@@ -731,6 +733,9 @@ class CidUserAnswer(models.Model):
|
|||||||
|
|
||||||
mark = "unmarked"
|
mark = "unmarked"
|
||||||
if marked_ans is not None:
|
if marked_ans is not None:
|
||||||
|
self.score = marked_ans.status
|
||||||
|
self.save()
|
||||||
|
|
||||||
if marked_ans.status == Answer.MarkOptions.CORRECT:
|
if marked_ans.status == Answer.MarkOptions.CORRECT:
|
||||||
mark = 2
|
mark = 2
|
||||||
elif marked_ans.status == Answer.MarkOptions.HALF_MARK:
|
elif marked_ans.status == Answer.MarkOptions.HALF_MARK:
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
<th>Score</th>
|
<th>Score</th>
|
||||||
<th>Normalised Score</th>
|
<th>Normalised Score</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for user, value in user_answers_marks.items %}
|
{% for cid in cids %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'cid_scores_admin' user %}">{{user}}</a></td>
|
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
|
||||||
<td>{{user_scores|get_item:user}}</td>
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
<td>{{user_scores_normalised|get_item:user}}</td>
|
<td>{{user_scores_normalised|get_item:cid}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
@@ -63,7 +63,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% for cid in cids %}
|
{% for cid in cids %}
|
||||||
<td class="rapid-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
|
{% comment %} <td class="rapid-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td> {% endcomment %}
|
||||||
|
{% with by_question|get_item:question|get_item:cid as ans_score %}
|
||||||
|
<td class="anatomy-ans user-answer-score-{{ans_score.1}}" title="answer score: {{ans_score.1}}">{{ans_score.0}}</td>
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+1
-1
@@ -1048,7 +1048,7 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
|||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(
|
GenericExamViews = ExamViews(
|
||||||
Exam, Rapid, CidUserAnswer, "rapids", "rapid", loadJsonAnswer
|
Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid", loadJsonAnswer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -196,13 +196,16 @@ class CidUserAnswer(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}/{}/{}".format(self.cid, self.exam, self.question.pk)
|
return "{}/{}/{}".format(self.cid, self.exam, self.question.pk)
|
||||||
|
|
||||||
|
def get_answer_string(self):
|
||||||
|
return self.answer
|
||||||
|
|
||||||
def get_answer(self):
|
def get_answer(self):
|
||||||
return self.answer
|
return self.answer
|
||||||
|
|
||||||
def get_correct(self):
|
def get_correct(self):
|
||||||
return self.answer == self.question.best_answer
|
return self.answer == self.question.best_answer
|
||||||
|
|
||||||
def get_score(self):
|
def get_answer_score(self):
|
||||||
if self.get_correct():
|
if self.get_correct():
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{% extends 'sbas/exams.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="stats-plot">{{plot|safe}}</div>
|
||||||
|
<div class="sbas">
|
||||||
|
<h2>{{ exam.name }}</h2>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="stats-block">
|
||||||
|
<h3>Stats</h3>
|
||||||
|
Candidates: {{cids|length}}<br />
|
||||||
|
Max score: {{max_score}}<br />
|
||||||
|
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm cid-score-table">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate ID</th>
|
||||||
|
<th>Score</th>
|
||||||
|
{% comment %} <th>Normalised Score</th> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% comment %} <td>{{user_scores_normalised|get_item:user}}</td> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Answers as a table</h3>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm col-sm">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate</th>
|
||||||
|
{% for cid in cids %}
|
||||||
|
{% comment %} <th><a href="{% url 'sbas:exam_scores_cid_user' exam.pk cid %}">{{cid}}</a></th> {% endcomment %}
|
||||||
|
<th><a href="">{{cid}}</a></th>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for question in questions %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="">Question {{forloop.counter}}</a>
|
||||||
|
</td>
|
||||||
|
{% comment %} {% for cid in cids %}
|
||||||
|
<td class="sbas-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
|
||||||
|
{% endfor %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
{% with by_question|get_item:question|get_item:cid as ans_score %}
|
||||||
|
<td class="sbas-ans user-answer-score-{{ans_score.1}}" title="answer score: {{ans_score.1}}">{{ans_score.0}}</td>
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td>Score:</td>
|
||||||
|
{% for cid in cids %}
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
+1
-1
@@ -28,7 +28,7 @@ urlpatterns = [
|
|||||||
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores",
|
"exam/<int:pk>/scores",
|
||||||
cache_page(60 * 1)(views.exam_scores_cid),
|
views.GenericExamViews.exam_scores_cid,
|
||||||
name="exam_scores_cid",
|
name="exam_scores_cid",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|||||||
+1
-1
@@ -383,7 +383,7 @@ def loadJsonAnswer(answer):
|
|||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(
|
GenericExamViews = ExamViews(
|
||||||
Exam, Question, CidUserAnswer, "sbas", "sbas", loadJsonAnswer
|
Exam, Question, None, CidUserAnswer, "sbas", "sbas", loadJsonAnswer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user