diff --git a/longs/models.py b/longs/models.py index c7fc639d..f1373745 100644 --- a/longs/models.py +++ b/longs/models.py @@ -205,6 +205,7 @@ class Long(models.Model): #"feedback_image": [], #"annotations": [str(q.image_annotations)], "type": "long", + "cached": False, } return exam_question @@ -393,14 +394,10 @@ class LongSeries(models.Model): else: skipcount = skipcount + 1 - print("skipped, no SliceLocation: {}".format(skipcount)) + print("skipped, no {}: {}".format(field, skipcount)) # ensure they are in the correct order slices = sorted(slices, key=lambda s: s[field].value) - #if field == "SliceLocation": - # slices = sorted(slices, key=lambda s: s.SliceLocation) - #elif field == "InstanceNumber": - # slices = sorted(slices, key=lambda s: s.InstanceNumber) #print(slices) @@ -632,12 +629,6 @@ class CidUserAnswer(models.Model): ans = ans.strip() - # def get_compare_string(self): - # # strip here should be unneccasry (providing clean is now working) - # s = self.answer.lower().strip() - # s = s.translate(str.maketrans('', '', string.punctuation)) - # return s - def is_marked(self): if self.score == "": return False @@ -645,4 +636,6 @@ class CidUserAnswer(models.Model): return True def get_answer_score(self): + if self.score == "": + return "" return float(self.score) diff --git a/longs/templates/longs/exam_scores_user.html b/longs/templates/longs/exam_scores_user.html index 86936e20..0fca8f71 100644 --- a/longs/templates/longs/exam_scores_user.html +++ b/longs/templates/longs/exam_scores_user.html @@ -4,13 +4,28 @@

Exam: {{ exam.name }}

Candidate: {{ cid }}

- Answers: + Scores:
Total mark: {{ total_score }} / {{max_score}} +
+

Answers

+ + +
Other exams
diff --git a/longs/templates/longs/long_series_viewer.html b/longs/templates/longs/long_series_viewer.html index 425599a9..9617b76f 100755 --- a/longs/templates/longs/long_series_viewer.html +++ b/longs/templates/longs/long_series_viewer.html @@ -17,6 +17,7 @@ This series is not associated with any cases.
Order dicoms by slice location Order dicoms by instance number +Order dicoms by SeriesInstanceUID Order by uploaded filename
{% for image in series.images.all %} diff --git a/longs/urls.py b/longs/urls.py index 3ed91a4e..f331d6ce 100755 --- a/longs/urls.py +++ b/longs/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path("series/", views.long_series_detail, name="long_series_detail"), path("series//order_dicom", views.long_series_order_dicom, name="long_series_order_dicom"), path("series//order_dicom_instance", views.long_series_order_dicom_instance, name="long_series_order_dicom_instance"), + path("series//order_dicom_SeriesInstanceUID", views.long_series_order_dicom_SeriesInstanceUID, name="long_series_order_dicom_SeriesInstanceUID"), path("series//order_upload_filename", views.long_series_order_upload_filename, name="long_series_order_upload_filename"), path("series//delete", views.LongSeriesDelete.as_view(), name="long_series_delete"), # path("unchecked/", views.unchecked_list, name="unchecked_list"), diff --git a/longs/views.py b/longs/views.py index 24e73b7e..bb82ac6f 100755 --- a/longs/views.py +++ b/longs/views.py @@ -889,21 +889,27 @@ def exam_scores_cid_user(request, pk, sk): #answers_and_marks = [] answers_marks = [] #answers = [] + answer_text = [] for q in questions: # Get user answer user_answer = q.cid_user_answers.filter(cid=cid).first() + + if not user_answer or user_answer is None: # skip if no answer #answers_marks.append("") #answers.append("") answer_score = 4 #ans = "Not answered" + answer_text.append((("Not answered"),("Not answered"),("Not answered"),("Not answered"),("Not answered"))) else: answer_score = user_answer.get_answer_score() + answer_text.append((user_answer.answer_observations, user_answer.answer_interpretation, user_answer.answer_principle_diagnosis, user_answer.answer_differential_diagnosis, user_answer.answer_management)) + if not exam.publish_results: answer_score = 0 #answers.append(ans) @@ -931,6 +937,7 @@ def exam_scores_cid_user(request, pk, sk): "answers_marks": answers_marks, "total_score": total_score, "max_score": max_score, + "answer_text": answer_text #"answers_and_marks": answers_and_marks, }, ) @@ -955,6 +962,16 @@ def long_series_order_dicom_instance(request, pk): return redirect("longs:long_series_detail", pk=pk) +@login_required +def long_series_order_dicom_SeriesInstanceUID(request, pk): + series = get_object_or_404(LongSeries, pk=pk) + try: + series.order_by_dicom("SeriesInstanceUID") + except: + return HttpResponse('

Series does not appear to contain dicoms (or field SeriesInstanceUID)

') + + return redirect("longs:long_series_detail", pk=pk) + @login_required def long_series_order_upload_filename(request, pk): series = get_object_or_404(LongSeries, pk=pk) @@ -986,6 +1003,9 @@ def question_json(request, pk): cache.set("{}_question_json_{}".format("longs", pk), question_json, 3600) + question.recreate_json = False + question.save() + return JsonResponse(question_json) @login_required diff --git a/rapids/admin.py b/rapids/admin.py index 75d4d0b1..0cf20a6e 100755 --- a/rapids/admin.py +++ b/rapids/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault, Answer, Exam +from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault, Answer, Exam, CidUserAnswer import tagulous.admin @@ -72,3 +72,9 @@ class RapidAdmin(VersionAdmin): admin.site.register(Rapid, RapidAdmin) #tagulous.admin.register(Rapid.condition) + +class CidUserAnswerAdmin(admin.ModelAdmin): + exclude = [] + readonly_fields = ["created", "updated"] + +admin.site.register(CidUserAnswer, CidUserAnswerAdmin) \ No newline at end of file diff --git a/rapids/forms.py b/rapids/forms.py index 41e03f92..dafba32e 100755 --- a/rapids/forms.py +++ b/rapids/forms.py @@ -65,13 +65,13 @@ class AbnormalityForm(ModelForm): class RapidCreationDefaultForm(ModelForm): class Meta: model = RapidCreationDefault - #fields = ["site"] + # fields = ["site"] exclude = ["author"] class RapidForm(ModelForm): - exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False)) + # exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False)) class Media: # Django also includes a few javascript files necessary # for the operation of this form element. You need to @@ -84,11 +84,6 @@ class RapidForm(ModelForm): js = ["jsi18n.js", "tesseract.min.js"] def __init__(self, *args, **kwargs): - ModelForm.__init__(self, *args, **kwargs) - super(RapidForm, self).__init__(*args, **kwargs) - # self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100} - # self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100} - if kwargs.get("instance"): # We get the 'initial' keyword argument or initialize it # as a dict if it didn't exist. @@ -97,6 +92,11 @@ class RapidForm(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(RapidForm, self).__init__(*args, **kwargs) + # self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100} + # self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100} + self.fields["abnormality"] = ModelMultipleChoiceField( required=False, queryset=Abnormality.objects.all(), @@ -117,7 +117,11 @@ class RapidForm(ModelForm): self.fields["laterality"] = ChoiceField( choices=Rapid.LATERALITY_CHOICES, required=False, widget=RadioSelect() ) - + self.fields["exams"] = ModelMultipleChoiceField( + required=False, + queryset=Exam.objects.all(), + widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False), + ) def save(self, commit=True): # Get the unsaved Pizza instance @@ -152,7 +156,7 @@ class RapidForm(ModelForm): "region", "laterality", "examination", - #"site", + # "site", "feedback", ] # fields = ['question', 'feedback', 'subspecialty', 'references'] @@ -185,7 +189,7 @@ AnswerFormSet = inlineformset_factory( }, exclude=[], can_delete=True, - extra=1, + extra=0, max_num=10, ) diff --git a/rapids/models.py b/rapids/models.py index 894eac85..a72dc6cd 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -99,7 +99,7 @@ class Answer(models.Model): self.answer = self.answer.strip() s = self.answer.lower() - s = s.translate(str.maketrans('', '', string.punctuation)) + s = s.translate(str.maketrans('', '', string.punctuation.replace("#", ""))) self.answer_compare = s @@ -352,6 +352,21 @@ class Exam(ExamBase): help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100 ) + def get_normal_abnormal_breakdown(self): + # Inefficient but more extendible + questions = self.exam_questions.all() + + normal = [] + abnormal = [] + for q in questions: + if q.normal: + normal.append(q) + else: + abnormal.append(q) + + return len(normal) + + def get_exam_json(self): questions = self.exam_questions.all() @@ -443,7 +458,7 @@ class CidUserAnswer(models.Model): self.answer = self.answer.strip() s = self.answer.lower() - s = s.translate(str.maketrans('', '', string.punctuation)) + s = s.translate(str.maketrans('', '', string.punctuation.replace("#", ""))) self.answer_compare = s diff --git a/rapids/templates/rapids/exam_overview.html b/rapids/templates/rapids/exam_overview.html index b915f176..f20873b8 100644 --- a/rapids/templates/rapids/exam_overview.html +++ b/rapids/templates/rapids/exam_overview.html @@ -6,7 +6,8 @@
Admin Edit

Exam: {{ exam.name }}

- This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds. + This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.
+ Normal {{ exam.get_normal_abnormal_breakdown }}
Exam active: [When checked the exam will be available to take in the test system] diff --git a/rapids/views.py b/rapids/views.py index b0400c81..4f52758e 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -355,6 +355,10 @@ class RapidUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView): return context def form_valid(self, form): + # save exam orders (there must be a better way to do this) + exam_orders = {} + for exam in self.object.exams.all(): + exam_orders[exam] = list(exam.exam_questions.all()) self.object = form.save(commit=False) self.object.save() @@ -371,6 +375,14 @@ class RapidUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView): image_formset.save() answer_formset.instance = self.object answer_formset.save() + + # restore exam orders + for exam in self.object.exams.all(): + if exam in exam_orders and self.object in exam_orders[exam]: + print(exam_orders[exam]) + exam.exam_questions.set(exam_orders[exam]) + exam.save() + return response else: return super().form_invalid(form) @@ -529,15 +541,14 @@ def loadJsonAnswer(answer): posted_answer = answer["ans"] + # qidn 1 does not hold answer data (but should always arrive prior to 2) + if answer["qidn"] == "1": + posted_answer = "" + # Normal answers are just posted with the answer "Normal" normal = False if posted_answer == "Normal": normal = True - posted_answer = "" - # We can ignore the first ab - elif answer["qidn"] == "1" and posted_answer == "Abnormal": - return False - if not exiting_answers: ans = CidUserAnswer(answer=posted_answer, normal=normal, cid=answer["cid"]) @@ -551,8 +562,10 @@ def loadJsonAnswer(answer): # Update an existing answer # should never be more than one (famous last words) ans = exiting_answers[0] - ans.normal = normal - ans.answer = posted_answer + if answer["qidn"] == "1": + ans.normal = normal + elif answer["qidn"] == "2": + ans.answer = posted_answer ans.full_clean() ans.save() diff --git a/templates/index.html b/templates/index.html index f9266e0e..87e5eff7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,9 @@ Anatomy Physics Rapids + {% if not request.user.is_authenticated %} Log in + {% endif %} {% if request.user.is_staff %}Admin{% endif %} Reset password