Merge branch 'master' of ssh://161.35.163.87:/home/django/rad

This commit is contained in:
Ross
2021-03-08 11:43:38 +00:00
11 changed files with 104 additions and 33 deletions
+4 -11
View File
@@ -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)
+16 -1
View File
@@ -4,13 +4,28 @@
<div class="longs">
<h2>Exam: {{ exam.name }}</h2>
<h3>Candidate: {{ cid }}</h3>
Answers:
Scores:
<ul>{% for score in answers_marks %}
<li class="user-answer-li">Question {{forloop.counter}}</li>
<span class="user-answer-score user-answer-score-{{score}}"><pre>{{ans}}</pre> ({{score}})</span>
{% endfor %}
</ul>
<br /> Total mark: {{ total_score }} / {{max_score}}
<div>
<h4>Answers</h4>
<ul class="long-answer">{% for a,b,c,d,e in answer_text %}
<li class="user-answer-li"><b>Question {{forloop.counter}}</b></li>
<ul>
<li class="user-answer-li">Observation</br><pre>{{a}}</pre></li>
<li class="user-answer-li">Interpretation</br><pre>{{b}}</pre></li>
<li class="user-answer-li">Principle Diagnosis</br><pre>{{c}}</pre></li>
<li class="user-answer-li">Differential Diagnosis</br><pre>{{d}}</pre></li>
<li class="user-answer-li">Management</br><pre>{{e}}</pre></li>
</ul>
{% endfor %}
</ul>
</div>
<div>
<a href="{% url 'cid_scores' cid %}">Other exams</a>
</div>
@@ -17,6 +17,7 @@ This series is not associated with any cases.
<div>
<a href="{% url 'longs:long_series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms by slice location</a>
<a href="{% url 'longs:long_series_order_dicom_instance' pk=series.pk %}" title="orders dicom by instance number">Order dicoms by instance number</a>
<a href="{% url 'longs:long_series_order_dicom_SeriesInstanceUID' pk=series.pk %}" title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
<a href="{% url 'longs:long_series_order_upload_filename' pk=series.pk %}" title="orders dicom by uploaded filename">Order by uploaded filename</a>
</div>
{% for image in series.images.all %}
+1
View File
@@ -13,6 +13,7 @@ urlpatterns = [
path("series/<int:pk>", views.long_series_detail, name="long_series_detail"),
path("series/<int:pk>/order_dicom", views.long_series_order_dicom, name="long_series_order_dicom"),
path("series/<int:pk>/order_dicom_instance", views.long_series_order_dicom_instance, name="long_series_order_dicom_instance"),
path("series/<int:pk>/order_dicom_SeriesInstanceUID", views.long_series_order_dicom_SeriesInstanceUID, name="long_series_order_dicom_SeriesInstanceUID"),
path("series/<int:pk>/order_upload_filename", views.long_series_order_upload_filename, name="long_series_order_upload_filename"),
path("series/<int:pk>/delete", views.LongSeriesDelete.as_view(), name="long_series_delete"),
# path("unchecked/", views.unchecked_list, name="unchecked_list"),
+20
View File
@@ -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('<h1>Series does not appear to contain dicoms (or field SeriesInstanceUID)</h1>')
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
+7 -1
View File
@@ -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)
+14 -10
View File
@@ -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,
)
+17 -2
View File
@@ -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
+2 -1
View File
@@ -6,7 +6,8 @@
<div class="rapids">
<a href="{% url 'admin:rapids_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
<h1>Exam: {{ exam.name }}</h1>
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.<br/>
Normal {{ exam.get_normal_abnormal_breakdown }}
<div class="parent-help" title="Click to enable / disable the exam">
Exam active: <input type="checkbox" id="exam-active-switch" {% if exam.active %}checked{% endif %}> <span class="help-text">[When checked the exam will be available to take in the test system]</span>
+20 -7
View File
@@ -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()
+2
View File
@@ -6,7 +6,9 @@
<a href="{% url 'anatomy:index'%}">Anatomy</a>
<a href="{% url 'physics:index'%}">Physics</a>
<a href="{% url 'rapids:index'%}">Rapids</a>
{% if not request.user.is_authenticated %}
<a href="{% url 'login'%}">Log in</a>
{% endif %}
{% if request.user.is_staff %}<a href="{% url 'admin:index'%}">Admin</a>{% endif %}
<a href="{% url 'password_reset'%}">Reset password</a>
</div>