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 />
Score: {{ciduseranswer.get_answer_score}}<br />
<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 %}
+26 -5
View File
@@ -72,9 +72,9 @@ from .models import (
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 longs.models import Long as LongQuestion
from longs.models import Long as LongQuestion, LongSeries
from longs.models import Exam as LongExam
from anatomy.models import AnatomyQuestion as AnatomyQuestion
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":
questions = (
exam.exam_questions.select_related()
@@ -812,6 +832,7 @@ class ExamViews(View, LoginRequiredMixin):
"abnormality",
"region",
"examination",
"author"
)
)
# questions = (
@@ -1868,7 +1889,7 @@ class ExamViews(View, LoginRequiredMixin):
user_answers_callstates = defaultdict(list)
user_answers_callstates_counted = {}
if self.app_name in ("physics", "sbas"):
if self.app_name in ("physics", "sbas", "longs"):
cached_scores = False
questions = exam.exam_questions.all()
else:
@@ -1932,7 +1953,7 @@ class ExamViews(View, LoginRequiredMixin):
callstate = s.get_answer_callstate()
by_question[q][cid] = (ans, answer_score, 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)
else:
zipped_ans_scores = zip(ans, answer_score)
@@ -1942,7 +1963,7 @@ class ExamViews(View, LoginRequiredMixin):
user_scores_normalised = {}
user_answer_count = {}
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(
[i for i in user_answers_marks[user] if i != "unmarked"]
)
+36
View File
@@ -1,4 +1,5 @@
from django.forms import (
BaseInlineFormSet,
Form,
ModelForm,
ModelMultipleChoiceField,
@@ -20,6 +21,9 @@ from longs.models import (
Exam,
)
from django.db.models import Prefetch
from anatomy.models import Modality
from generic.models import Examination#, Condition, Sign
@@ -174,9 +178,11 @@ class LongForm(ModelForm):
# a list of primary key for the selected data.
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
ModelForm.__init__(self, *args, **kwargs)
super(LongForm, self).__init__(*args, **kwargs)
print(self.fields)
if self.user.groups.filter(name="long_checker").exists():
exam_queryset = Exam.objects.all()
@@ -245,14 +251,44 @@ class LongForm(ModelForm):
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(
Long,
LongSeries.long.through,
form=CaseSeriesForm,
exclude=[],
can_delete=True,
extra=0,
max_num=10,
#formset=CustomInlineFormSet
)
LongSeriesImageFormSet = inlineformset_factory(
+36 -20
View File
@@ -32,8 +32,8 @@ from generic.models import (
CidUserGroup,
ExamUserStatus,
Examination,
#Condition,
#Sign,
# Condition,
# Sign,
ExamBase,
Plane,
Contrast,
@@ -90,15 +90,15 @@ class Long(models.Model):
feedback = models.TextField(null=True, blank=True)
# TODO: merge with atlas condition / signs / finding
#condition = tagulous.models.TagField(
# condition = tagulous.models.TagField(
# to=Condition,
# blank=True,
# 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"
#)
# )
# Model answers
model_observations = models.TextField(null=True, blank=True)
@@ -224,16 +224,17 @@ class Long(models.Model):
"management": self.model_management,
}
def get_question_json(self, based: bool=True, answers: bool=True, feedback: bool=False):
def get_question_json(
self, based: bool = True, answers: bool = True, feedback: bool = False
):
"""Returns a json representation of the question
if based = False return is a dict
otherwise a url to the json file
answers arg is only parsed if based = False
"""
question_id = self.pk
if not based:
@@ -285,7 +286,6 @@ class Long(models.Model):
Path(path).parents[0].mkdir(parents=True, exist_ok=True)
with open(path, "w+") as f:
print("start writing file")
# We manually create the json for long questions to reducem memroy usade
f.write(
@@ -431,13 +431,13 @@ class LongSeries(models.Model):
)
def __str__(self):
if self.long:
long_id = ", ".format([long.pk for long in self.long.all()])
# long_id = self.long.pk
else:
long_id = "None"
return "{}/{} : {} [{}]".format(
self.pk, self.get_examination(), self.description, long_id
#if self.long:
# long_id = ", ".format([long.pk for long in self.long.all()])
# # long_id = self.long.pk
#else:
# long_id = "None"
return "{}/{} : {}".format(
self.pk, self.get_examination(), self.description
)
def get_author_objects(self):
@@ -695,7 +695,6 @@ class Exam(ExamBase):
for q in questions:
exam_order.append(q.id)
if based:
# If it is a new question we need to for a question json refresh
if q.json_creation_time is None:
q.get_question_json()
@@ -755,6 +754,7 @@ class Exam(ExamBase):
@reversion.register
class UserAnswer(UserAnswerBase):
"""User answers by candidate"""
app_name = "longs"
question = models.ForeignKey(
@@ -869,7 +869,7 @@ class UserAnswer(UserAnswerBase):
def get_answer_score(self):
if self.score == "":
return ""
return "unmarked"
return float(self.score)
def discrepant_answers(self):
@@ -878,7 +878,23 @@ class UserAnswer(UserAnswerBase):
return True
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
+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 %}
<div class="longs">
@@ -8,7 +31,7 @@
<div class="alert alert-warning" role="alert">
The following questions need marking
{% 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 %}
</div>
@@ -53,7 +76,7 @@
</thead>
{% for question in questions %}
<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>
{% for ans, score in by_question|get_item:question %}
<td class="user-answer-score-{{score}}" title="answer score: {{score}}">{{score}}</td>
@@ -69,4 +92,4 @@
</table>
</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>
{% if request.user.is_superuser %}
<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
Edit (user answer)</a>
{% 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>
{% if discrepancy_form %}
@@ -39,7 +39,7 @@
{% if not next_unmarked_id and not unmarked %}
<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/>
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><br />
{% if previous_answer_id %}
+2 -2
View File
@@ -8,13 +8,13 @@
<div>
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
</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>
<ul id="question-mark-list">
{% for question, unmarked_count, unmarked_count_marker in question_unmarked_map %}
<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}}
{% if exam.double_mark %}
<br/>
@@ -32,12 +32,12 @@
</ul>
<div>
{% 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>
{% endif %}
{% if question_details.current >= question_details.total %}
{% 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 %}
</div>
</div>
@@ -32,12 +32,12 @@
</ul>
<div>
{% 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>
{% endif %}
{% if question_details.current >= question_details.total %}
{% 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 %}
</div>
</div>
+8 -8
View File
@@ -79,8 +79,8 @@ urlpatterns = [
),
path(
"exam/<int:exam_id>/<int:sk>/mark",
views.mark_question_overview,
name="mark_question_overview",
views.mark,
name="mark",
),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path(
@@ -88,12 +88,12 @@ urlpatterns = [
views.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/<int:cid>/<str:passcode>/",
views.exam_scores_cid_user,
name="exam_scores_cid_user",
),
#path("exam/<int:pk>/scores", views.exam_scores_all, name="exam_scores_all"),
# path(
# "exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
# views.exam_scores_cid_user,
# name="exam_scores_cid_user",
# ),
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: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):
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:
context["series_formset"] = SeriesFormSet(
self.request.POST, self.request.FILES
self.request.POST, self.request.FILES, queryset=queryset
)
context["series_formset"].full_clean()
else:
context["series_formset"] = SeriesFormSet()
context["series_formset"] = SeriesFormSet(queryset=queryset)
return context
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,
)
# 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:
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,
)
# 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:
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")
@login_required
@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)
questions = exam.exam_questions.all()
@@ -864,113 +869,113 @@ def mark_question_overview(request, exam_id, sk):
)
@login_required
@user_is_long_marker
def exam_scores_all(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
cids = (
UserAnswer.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, (score 4)
user_answers_marks[cid].append(4)
# user_answers[cid].append("")
by_question[q].append(("", 4))
continue
else:
ans = ""
answer_score = s.get_answer_score()
if answer_score == "":
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 = {}
user_scores_normalised = {}
for user in user_answers_marks:
user_scores[user] = sum([i for i in user_answers_marks[user] if i != ""])
user_scores_normalised[user] = normaliseScore(
sum([i for i in user_answers_marks[user] if i != ""])
)
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
return render(
request,
"longs/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_normalised": user_scores_normalised,
"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
#@user_is_long_marker
#def exam_scores_all(request, pk):
# exam = get_object_or_404(Exam, pk=pk)
#
# questions = exam.exam_questions.all()
#
# cids = (
# UserAnswer.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, (score 4)
# user_answers_marks[cid].append(4)
# # user_answers[cid].append("")
# by_question[q].append(("", 4))
# continue
# else:
# ans = ""
# answer_score = s.get_answer_score()
# if answer_score == "":
# 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 = {}
# user_scores_normalised = {}
# for user in user_answers_marks:
# user_scores[user] = sum([i for i in user_answers_marks[user] if i != ""])
# user_scores_normalised[user] = normaliseScore(
# sum([i for i in user_answers_marks[user] if i != ""])
# )
#
# 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
#
# return render(
# request,
# "longs/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_normalised": user_scores_normalised,
# "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, cid, passcode):