many Longs fixes

This commit is contained in:
Ross
2023-05-22 11:21:56 +01:00
parent 56eb5f8230
commit 4345f46615
11 changed files with 260 additions and 159 deletions
@@ -7,5 +7,5 @@
Answer: {{ciduseranswer.answer}}<br /> Answer: {{ciduseranswer.answer}}<br />
Score: {{ciduseranswer.get_answer_score}}<br /> Score: {{ciduseranswer.get_answer_score}}<br />
<a href="{% url 'anatomy:user_answer_delete' ciduseranswer.id %}">Delete answer</a> <a href="{% url 'anatomy:user_answer_delete' ciduseranswer.id %}">Delete answer</a>
<a href="{% url 'admin:anatomy_ciduseranswer_change' ciduseranswer.id %}">Admin Edit</a> <a href="{% url 'admin:anatomy_useranswer_change' ciduseranswer.id %}">Admin Edit</a>
{% endblock content %} {% endblock content %}
+26 -5
View File
@@ -72,9 +72,9 @@ from .models import (
get_next_cid, get_next_cid,
) )
from rapids.models import Rapid as RapidQuestion from rapids.models import Rapid as RapidQuestion, RapidImage
from rapids.models import Exam as RapidExam from rapids.models import Exam as RapidExam
from longs.models import Long as LongQuestion from longs.models import Long as LongQuestion, LongSeries
from longs.models import Exam as LongExam from longs.models import Exam as LongExam
from anatomy.models import AnatomyQuestion as AnatomyQuestion from anatomy.models import AnatomyQuestion as AnatomyQuestion
from anatomy.models import Exam as AnatomyExam from anatomy.models import Exam as AnatomyExam
@@ -795,6 +795,26 @@ class ExamViews(View, LoginRequiredMixin):
), ),
) )
) )
elif self.app_name == "longs":
questions = (
exam.exam_questions.select_related()
.prefetch_related(
Prefetch(
"series",
queryset=LongSeries.objects.select_related("modality", "examination", "plane", "contrast").prefetch_related(
Prefetch("images")
),
),
"author"
#to_attr="prefetched_primary_answer"
# queryset=self.Answer.objects.filter(),
#"series",
#"abnormality",
#"region",
#"examination",
)
.all()
)
elif self.app_name == "rapids": elif self.app_name == "rapids":
questions = ( questions = (
exam.exam_questions.select_related() exam.exam_questions.select_related()
@@ -812,6 +832,7 @@ class ExamViews(View, LoginRequiredMixin):
"abnormality", "abnormality",
"region", "region",
"examination", "examination",
"author"
) )
) )
# questions = ( # questions = (
@@ -1868,7 +1889,7 @@ class ExamViews(View, LoginRequiredMixin):
user_answers_callstates = defaultdict(list) user_answers_callstates = defaultdict(list)
user_answers_callstates_counted = {} user_answers_callstates_counted = {}
if self.app_name in ("physics", "sbas"): if self.app_name in ("physics", "sbas", "longs"):
cached_scores = False cached_scores = False
questions = exam.exam_questions.all() questions = exam.exam_questions.all()
else: else:
@@ -1932,7 +1953,7 @@ class ExamViews(View, LoginRequiredMixin):
callstate = s.get_answer_callstate() callstate = s.get_answer_callstate()
by_question[q][cid] = (ans, answer_score, callstate) by_question[q][cid] = (ans, answer_score, callstate)
user_answers_callstates[cid].append(callstate) user_answers_callstates[cid].append(callstate)
elif self.app_name in ("rapids", "anatomy", "sbas"): elif self.app_name in ("anatomy", "sbas", "longs"):
by_question[q][cid] = (ans, answer_score) by_question[q][cid] = (ans, answer_score)
else: else:
zipped_ans_scores = zip(ans, answer_score) zipped_ans_scores = zip(ans, answer_score)
@@ -1942,7 +1963,7 @@ class ExamViews(View, LoginRequiredMixin):
user_scores_normalised = {} user_scores_normalised = {}
user_answer_count = {} user_answer_count = {}
for user in user_answers_marks: for user in user_answers_marks:
if self.app_name in ("rapids", "anatomy", "sbas"): if self.app_name in ("rapids", "anatomy", "sbas", "longs"):
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"]
) )
+36
View File
@@ -1,4 +1,5 @@
from django.forms import ( from django.forms import (
BaseInlineFormSet,
Form, Form,
ModelForm, ModelForm,
ModelMultipleChoiceField, ModelMultipleChoiceField,
@@ -20,6 +21,9 @@ from longs.models import (
Exam, Exam,
) )
from django.db.models import Prefetch
from anatomy.models import Modality from anatomy.models import Modality
from generic.models import Examination#, Condition, Sign from generic.models import Examination#, Condition, Sign
@@ -174,9 +178,11 @@ class LongForm(ModelForm):
# a list of primary key for the selected data. # a list of primary key for the selected data.
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()] initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
ModelForm.__init__(self, *args, **kwargs) ModelForm.__init__(self, *args, **kwargs)
super(LongForm, self).__init__(*args, **kwargs) super(LongForm, self).__init__(*args, **kwargs)
print(self.fields)
if self.user.groups.filter(name="long_checker").exists(): if self.user.groups.filter(name="long_checker").exists():
exam_queryset = Exam.objects.all() exam_queryset = Exam.objects.all()
@@ -245,14 +251,44 @@ class LongForm(ModelForm):
return instance return instance
#class CustomInlineFormSet(BaseInlineFormSet):
# def get_queryset(self):
# if not hasattr(self, '_queryset'):
# qs = super(CustomInlineFormSet, self).get_queryset()#.values()
# self._queryset = qs
# return self._queryset
#class LongForm(ModelForm):
class CaseSeriesForm(ModelForm):
def __init__(self, *args, **kwargs):
super(CaseSeriesForm, self).__init__(*args, **kwargs)
ModelForm.__init__(self, *args, **kwargs)
# We can limit the series available to choose from here
# as well a dramatically reducing the sql queries
queryset = LongSeries.objects.select_related("modality", "examination", "plane").prefetch_related("long").all()#.filter(pk=62)#.values()
self.fields["longseries"] = ModelChoiceField(queryset=queryset)
class Meta:
model = LongSeries.long.through
#fields = [ modality ]
#exclude = []
fields = ["longseries", "sort_value"]
SeriesFormSet = inlineformset_factory( SeriesFormSet = inlineformset_factory(
Long, Long,
LongSeries.long.through, LongSeries.long.through,
form=CaseSeriesForm,
exclude=[], exclude=[],
can_delete=True, can_delete=True,
extra=0, extra=0,
max_num=10, max_num=10,
#formset=CustomInlineFormSet
) )
LongSeriesImageFormSet = inlineformset_factory( LongSeriesImageFormSet = inlineformset_factory(
+36 -20
View File
@@ -32,8 +32,8 @@ from generic.models import (
CidUserGroup, CidUserGroup,
ExamUserStatus, ExamUserStatus,
Examination, Examination,
#Condition, # Condition,
#Sign, # Sign,
ExamBase, ExamBase,
Plane, Plane,
Contrast, Contrast,
@@ -90,15 +90,15 @@ class Long(models.Model):
feedback = models.TextField(null=True, blank=True) feedback = models.TextField(null=True, blank=True)
# TODO: merge with atlas condition / signs / finding # TODO: merge with atlas condition / signs / finding
#condition = tagulous.models.TagField( # condition = tagulous.models.TagField(
# to=Condition, # to=Condition,
# blank=True, # blank=True,
# help_text='Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes "..."', # help_text='Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes "..."',
#) # )
#sign = tagulous.models.TagField( # sign = tagulous.models.TagField(
# to=Sign, blank=True, help_text="Radiological signs in the question" # to=Sign, blank=True, help_text="Radiological signs in the question"
#) # )
# Model answers # Model answers
model_observations = models.TextField(null=True, blank=True) model_observations = models.TextField(null=True, blank=True)
@@ -224,16 +224,17 @@ class Long(models.Model):
"management": self.model_management, "management": self.model_management,
} }
def get_question_json(
def get_question_json(self, based: bool=True, answers: bool=True, feedback: bool=False): self, based: bool = True, answers: bool = True, feedback: bool = False
):
"""Returns a json representation of the question """Returns a json representation of the question
if based = False return is a dict if based = False return is a dict
otherwise a url to the json file otherwise a url to the json file
answers arg is only parsed if based = False answers arg is only parsed if based = False
""" """
question_id = self.pk question_id = self.pk
if not based: if not based:
@@ -285,7 +286,6 @@ class Long(models.Model):
Path(path).parents[0].mkdir(parents=True, exist_ok=True) Path(path).parents[0].mkdir(parents=True, exist_ok=True)
with open(path, "w+") as f: with open(path, "w+") as f:
print("start writing file") print("start writing file")
# We manually create the json for long questions to reducem memroy usade # We manually create the json for long questions to reducem memroy usade
f.write( f.write(
@@ -431,13 +431,13 @@ class LongSeries(models.Model):
) )
def __str__(self): def __str__(self):
if self.long: #if self.long:
long_id = ", ".format([long.pk for long in self.long.all()]) # long_id = ", ".format([long.pk for long in self.long.all()])
# long_id = self.long.pk # # long_id = self.long.pk
else: #else:
long_id = "None" # long_id = "None"
return "{}/{} : {} [{}]".format( return "{}/{} : {}".format(
self.pk, self.get_examination(), self.description, long_id self.pk, self.get_examination(), self.description
) )
def get_author_objects(self): def get_author_objects(self):
@@ -695,7 +695,6 @@ class Exam(ExamBase):
for q in questions: for q in questions:
exam_order.append(q.id) exam_order.append(q.id)
if based: if based:
# If it is a new question we need to for a question json refresh # If it is a new question we need to for a question json refresh
if q.json_creation_time is None: if q.json_creation_time is None:
q.get_question_json() q.get_question_json()
@@ -755,6 +754,7 @@ class Exam(ExamBase):
@reversion.register @reversion.register
class UserAnswer(UserAnswerBase): class UserAnswer(UserAnswerBase):
"""User answers by candidate""" """User answers by candidate"""
app_name = "longs" app_name = "longs"
question = models.ForeignKey( question = models.ForeignKey(
@@ -869,7 +869,7 @@ class UserAnswer(UserAnswerBase):
def get_answer_score(self): def get_answer_score(self):
if self.score == "": if self.score == "":
return "" return "unmarked"
return float(self.score) return float(self.score)
def discrepant_answers(self): def discrepant_answers(self):
@@ -878,7 +878,23 @@ class UserAnswer(UserAnswerBase):
return True return True
return False return False
def get_answer_string(self) -> str:
return f"""
Observations:
{self.answer_observations},
Interpretation:
{self.answer_interpretation},
Principle Diagnosis:
{self.answer_principle_diagnosis},
Differential:
{self.answer_differential_diagnosis},
Management:
{self.answer_management},
"""
@reversion.register @reversion.register
+27 -4
View File
@@ -1,4 +1,27 @@
{% extends 'longs/exams.html' %}
{% extends 'generic/exam_scores_base.html' %}
{% block table_answers %}
{% for question in questions %}
<tr>
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
</td>
{% 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>
{% endfor %} {% endcomment %}
{% for cid in cids %}
{% with by_question|get_item:question|get_item:cid as ans_score %}
<td class="longs-ans user-answer-score-{{ans_score.1}}" title="{{ans_score.0}}">{{ans_score.1}}</td>
{% endwith %}
{% endfor %}
</tr>
{% endfor %}
{% endblock table_answers %}
{% comment %} {% extends 'longs/exams.html' %}
{% block content %} {% block content %}
<div class="longs"> <div class="longs">
@@ -8,7 +31,7 @@
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
The following questions need marking The following questions need marking
{% for exam_index in unmarked %} {% for exam_index in unmarked %}
<a href="{% url 'longs:mark_question_overview' exam.pk exam_index %}">{{ exam_index|add:1 }}</a> <a href="{% url 'longs:mark' exam.pk exam_index %}">{{ exam_index|add:1 }}</a>
{% endfor %} {% endfor %}
</div> </div>
@@ -53,7 +76,7 @@
</thead> </thead>
{% for question in questions %} {% for question in questions %}
<tr> <tr>
<td><a href="{% url 'longs:mark_question_overview' exam_id=exam.pk sk=forloop.counter0 %}">Question <td><a href="{% url 'longs:mark' exam_id=exam.pk sk=forloop.counter0 %}">Question
{{forloop.counter}}</a></td> {{forloop.counter}}</a></td>
{% for ans, score in by_question|get_item:question %} {% for ans, score in by_question|get_item:question %}
<td class="user-answer-score-{{score}}" title="answer score: {{score}}">{{score}}</td> <td class="user-answer-score-{{score}}" title="answer score: {{score}}">{{score}}</td>
@@ -69,4 +92,4 @@
</table> </table>
</div> </div>
{% endblock %} {% endblock %} {% endcomment %}
+3 -3
View File
@@ -5,11 +5,11 @@
<a href="{% url 'longs:long_update' question.id %}" title="Edit the Question">Edit</a> <a href="{% url 'longs:long_update' question.id %}" title="Edit the Question">Edit</a>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<a href="{% url 'admin:longs_long_change' question.id %}" title="Edit the Question using the admin interface">Admin <a href="{% url 'admin:longs_long_change' question.id %}" title="Edit the Question using the admin interface">Admin
Edit</a> <a href="{% url 'admin:longs_ciduseranswer_change' answer.id %}" Edit</a> <a href="{% url 'admin:longs_useranswer_change' answer.id %}"
title="Edit the user answer using the admin interface">Admin title="Edit the user answer using the admin interface">Admin
Edit (user answer)</a> Edit (user answer)</a>
{% endif %} {% endif %}
<h2>Marking question <a href="{% url 'longs:mark_question_overview' exam.id question_details.current|add:'-1' %}" <h2>Marking question <a href="{% url 'longs:mark' exam.id question_details.current|add:'-1' %}"
title="View question answers">{{question_details.current}}</a> of {{question_details.total}}</h2> title="View question answers">{{question_details.current}}</a> of {{question_details.total}}</h2>
{% if discrepancy_form %} {% if discrepancy_form %}
@@ -39,7 +39,7 @@
{% if not next_unmarked_id and not unmarked %} {% if not next_unmarked_id and not unmarked %}
<div class="alert alert-info sticky-alert" role="alert">Success! Marking question complete. <br /> <div class="alert alert-info sticky-alert" role="alert">Success! Marking question complete. <br />
<a href="{% url 'longs:mark_answer_override' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=answer.id %}">Set final score</a><br/> <a href="{% url 'longs:mark_answer_override' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=answer.id %}">Set final score</a><br/>
Return to <a href="{% url 'longs:mark_question_overview' exam.id question_details.current|add:'-1' %}">question Return to <a href="{% url 'longs:mark' exam.id question_details.current|add:'-1' %}">question
overview</a>, <a href="{% url 'longs:mark_overview' pk=exam.pk %}">marking overview</a>, <a href="{% url 'longs:mark_overview' pk=exam.pk %}">marking
overview</a><br /> overview</a><br />
{% if previous_answer_id %} {% if previous_answer_id %}
+2 -2
View File
@@ -8,13 +8,13 @@
<div> <div>
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button> <button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
</div> </div>
<div id="stark-marking-button"><a href="{% url 'longs:mark_question_overview' exam_id=exam.pk sk=0 %}"><button>Click here to start <div id="stark-marking-button"><a href="{% url 'longs:mark' exam_id=exam.pk sk=0 %}"><button>Click here to start
marking</button></a></div> marking</button></a></div>
<ul id="question-mark-list"> <ul id="question-mark-list">
{% for question, unmarked_count, unmarked_count_marker in question_unmarked_map %} {% for question, unmarked_count, unmarked_count_marker in question_unmarked_map %}
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a <li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a
href="{% url 'longs:mark_question_overview' exam_id=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}: href="{% url 'longs:mark' exam_id=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> Answers incompletely marked: {{unmarked_count}} {{ question }}</a><br /> Answers incompletely marked: {{unmarked_count}}
{% if exam.double_mark %} {% if exam.double_mark %}
<br/> <br/>
@@ -32,12 +32,12 @@
</ul> </ul>
<div> <div>
{% if question_details.current > 1 %} {% if question_details.current > 1 %}
<a href="{% url 'longs:mark_question_overview' exam.id question_details.current|add:'-2' %}" <a href="{% url 'longs:mark' exam.id question_details.current|add:'-2' %}"
title="Previous question">Previous</a> title="Previous question">Previous</a>
{% endif %} {% endif %}
{% if question_details.current >= question_details.total %} {% if question_details.current >= question_details.total %}
{% else %} {% else %}
<a href="{% url 'longs:mark_question_overview' exam.id question_details.current %}" title="Next question">Next</a> <a href="{% url 'longs:mark' exam.id question_details.current %}" title="Next question">Next</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -32,12 +32,12 @@
</ul> </ul>
<div> <div>
{% if question_details.current > 1 %} {% if question_details.current > 1 %}
<a href="{% url 'longs:mark_question_overview' exam.id question_details.current|add:'-2' %}" <a href="{% url 'longs:mark' exam.id question_details.current|add:'-2' %}"
title="Previous question">Previous</a> title="Previous question">Previous</a>
{% endif %} {% endif %}
{% if question_details.current >= question_details.total %} {% if question_details.current >= question_details.total %}
{% else %} {% else %}
<a href="{% url 'longs:mark_question_overview' exam.id question_details.current %}" title="Next question">Next</a> <a href="{% url 'longs:mark' exam.id question_details.current %}" title="Next question">Next</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
+8 -8
View File
@@ -79,8 +79,8 @@ urlpatterns = [
), ),
path( path(
"exam/<int:exam_id>/<int:sk>/mark", "exam/<int:exam_id>/<int:sk>/mark",
views.mark_question_overview, views.mark,
name="mark_question_overview", name="mark",
), ),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path( path(
@@ -88,12 +88,12 @@ urlpatterns = [
views.refresh_exam_question_json, views.refresh_exam_question_json,
name="refresh_exam_question_json", name="refresh_exam_question_json",
), ),
path("exam/<int:pk>/scores", views.exam_scores_all, name="exam_scores_all"), #path("exam/<int:pk>/scores", views.exam_scores_all, name="exam_scores_all"),
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,
name="exam_scores_cid_user", # name="exam_scores_cid_user",
), # ),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"), path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
+117 -112
View File
@@ -409,13 +409,18 @@ class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(LongCreateBase, self).get_context_data(**kwargs) context = super(LongCreateBase, self).get_context_data(**kwargs)
queryset = LongSeries.objects.select_related("modality", "examination", "plane").prefetch_related("long").filter(pk=62)#.values()
print("TEST")
print(queryset)
#queryset = LongSeries.objects.all()
if self.request.POST: if self.request.POST:
context["series_formset"] = SeriesFormSet( context["series_formset"] = SeriesFormSet(
self.request.POST, self.request.FILES self.request.POST, self.request.FILES, queryset=queryset
) )
context["series_formset"].full_clean() context["series_formset"].full_clean()
else: else:
context["series_formset"] = SeriesFormSet() context["series_formset"] = SeriesFormSet(queryset=queryset)
return context return context
def form_valid(self, form): def form_valid(self, form):
@@ -713,7 +718,7 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
answer_id=answer_id, answer_id=answer_id,
) )
# elif "previous" in request.POST: # elif "previous" in request.POST:
# return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1) # return redirect("longs:mark", pk=exam_id, sk=n - 1)
else: else:
form = MarkLongQuestionSingleForm(initial={"score": answer.score}) form = MarkLongQuestionSingleForm(initial={"score": answer.score})
@@ -763,7 +768,7 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
answer_id=answer_id, answer_id=answer_id,
) )
# elif "previous" in request.POST: # elif "previous" in request.POST:
# return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1) # return redirect("longs:mark", pk=exam_id, sk=n - 1)
else: else:
try: try:
@@ -811,7 +816,7 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
# @user_passes_test(user_is_admin, login_url="/accounts/login") # @user_passes_test(user_is_admin, login_url="/accounts/login")
@login_required @login_required
@user_is_long_marker @user_is_long_marker
def mark_question_overview(request, exam_id, sk): def mark(request, exam_id, sk):
exam = get_object_or_404(Exam, pk=exam_id) exam = get_object_or_404(Exam, pk=exam_id)
questions = exam.exam_questions.all() questions = exam.exam_questions.all()
@@ -864,113 +869,113 @@ def mark_question_overview(request, exam_id, sk):
) )
@login_required #@login_required
@user_is_long_marker #@user_is_long_marker
def exam_scores_all(request, pk): #def exam_scores_all(request, pk):
exam = get_object_or_404(Exam, pk=pk) # exam = get_object_or_404(Exam, pk=pk)
#
questions = exam.exam_questions.all() # questions = exam.exam_questions.all()
#
cids = ( # cids = (
UserAnswer.objects.filter(question__in=questions, exam__id=pk) # UserAnswer.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, (score 4) # # skip if no answer, (score 4)
user_answers_marks[cid].append(4) # user_answers_marks[cid].append(4)
# user_answers[cid].append("") # # user_answers[cid].append("")
by_question[q].append(("", 4)) # by_question[q].append(("", 4))
continue # continue
else: # else:
ans = "" # ans = ""
answer_score = s.get_answer_score() # answer_score = s.get_answer_score()
if answer_score == "": # if answer_score == "":
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 = {}
user_scores_normalised = {} # user_scores_normalised = {}
for user in user_answers_marks: # for user in user_answers_marks:
user_scores[user] = sum([i for i in user_answers_marks[user] if i != ""]) # user_scores[user] = sum([i for i in user_answers_marks[user] if i != ""])
user_scores_normalised[user] = normaliseScore( # user_scores_normalised[user] = normaliseScore(
sum([i for i in user_answers_marks[user] if i != ""]) # sum([i for i in user_answers_marks[user] if i != ""])
) # )
#
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
#
return render( # return render(
request, # request,
"longs/exam_scores.html", # "longs/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_normalised": user_scores_normalised, # "user_scores_normalised": user_scores_normalised,
"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, cid, passcode): def exam_scores_cid_user(request, pk, cid, passcode):