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
+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):