diff --git a/anatomy/templates/anatomy/ciduseranswer_detail.html b/anatomy/templates/anatomy/ciduseranswer_detail.html index b3ca6cc0..26c7d5aa 100644 --- a/anatomy/templates/anatomy/ciduseranswer_detail.html +++ b/anatomy/templates/anatomy/ciduseranswer_detail.html @@ -7,5 +7,5 @@ Answer: {{ciduseranswer.answer}}
Score: {{ciduseranswer.get_answer_score}}
Delete answer - Admin Edit + Admin Edit {% endblock content %} diff --git a/generic/views.py b/generic/views.py index 7087e228..962783cc 100644 --- a/generic/views.py +++ b/generic/views.py @@ -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"] ) diff --git a/longs/forms.py b/longs/forms.py index 261987c6..0ddd2442 100755 --- a/longs/forms.py +++ b/longs/forms.py @@ -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( diff --git a/longs/models.py b/longs/models.py index 72f6a8f6..dee8bb37 100644 --- a/longs/models.py +++ b/longs/models.py @@ -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 diff --git a/longs/templates/longs/exam_scores.html b/longs/templates/longs/exam_scores.html index d0df36d2..233c5033 100644 --- a/longs/templates/longs/exam_scores.html +++ b/longs/templates/longs/exam_scores.html @@ -1,4 +1,27 @@ -{% extends 'longs/exams.html' %} + +{% extends 'generic/exam_scores_base.html' %} + +{% block table_answers %} + {% for question in questions %} + + Question {{forloop.counter}} + + {% 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.1}} + {% endwith %} + {% endfor %} + + {% endfor %} +{% endblock table_answers %} + + + + +{% comment %} {% extends 'longs/exams.html' %} {% block content %}
@@ -8,7 +31,7 @@ @@ -53,7 +76,7 @@ {% for question in questions %} - Question + Question {{forloop.counter}} {% for ans, score in by_question|get_item:question %} {{score}} @@ -69,4 +92,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} {% endcomment %} \ No newline at end of file diff --git a/longs/templates/longs/mark_answer.html b/longs/templates/longs/mark_answer.html index fcd9d636..b8711e4c 100644 --- a/longs/templates/longs/mark_answer.html +++ b/longs/templates/longs/mark_answer.html @@ -5,11 +5,11 @@ Edit {% if request.user.is_superuser %} Admin - Edit Admin Edit (user answer) {% endif %} -

Marking question Marking question {{question_details.current}} of {{question_details.total}}

{% if discrepancy_form %} @@ -39,7 +39,7 @@ {% if not next_unmarked_id and not unmarked %} diff --git a/longs/templates/longs/mark_question_single_overview.html b/longs/templates/longs/mark_question_single_overview.html index b358f518..0113c1e0 100644 --- a/longs/templates/longs/mark_question_single_overview.html +++ b/longs/templates/longs/mark_question_single_overview.html @@ -32,12 +32,12 @@
{% if question_details.current > 1 %} - Previous {% endif %} {% if question_details.current >= question_details.total %} {% else %} - Next + Next {% endif %}
diff --git a/longs/urls.py b/longs/urls.py index 895aaef8..249d2151 100755 --- a/longs/urls.py +++ b/longs/urls.py @@ -79,8 +79,8 @@ urlpatterns = [ ), path( "exam///mark", - views.mark_question_overview, - name="mark_question_overview", + views.mark, + name="mark", ), path("exam//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//scores", views.exam_scores_all, name="exam_scores_all"), - path( - "exam//scores///", - views.exam_scores_cid_user, - name="exam_scores_cid_user", - ), + #path("exam//scores", views.exam_scores_all, name="exam_scores_all"), +# path( +# "exam//scores///", +# views.exam_scores_cid_user, +# name="exam_scores_cid_user", +# ), path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam//clone", views.ExamClone.as_view(), name="exam_clone"), path("exam//update", views.ExamUpdate.as_view(), name="exam_update"), diff --git a/longs/views.py b/longs/views.py index 367cbe97..a4868864 100755 --- a/longs/views.py +++ b/longs/views.py @@ -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):