.
This commit is contained in:
+44
-1
@@ -213,6 +213,49 @@ class AnatomyQuestion(models.Model):
|
|||||||
def get_annotations(self):
|
def get_annotations(self):
|
||||||
return self.image_annotations
|
return self.image_annotations
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return "{}".format(self.description)
|
||||||
|
|
||||||
|
def get_correct_unstripped_answers(self):
|
||||||
|
return set(
|
||||||
|
[
|
||||||
|
str(i)
|
||||||
|
for i in self.answers.filter(proposed=False)
|
||||||
|
if i.status == i.MarkOptions.CORRECT
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_question_json(self, based=True):
|
||||||
|
"""Returns a json representation of the question"""
|
||||||
|
|
||||||
|
# Loop through rapidimage associations
|
||||||
|
images = []
|
||||||
|
annotations = []
|
||||||
|
feedback_images = []
|
||||||
|
|
||||||
|
if based:
|
||||||
|
images.append(image_as_base64(self.image))
|
||||||
|
else:
|
||||||
|
images.append("{}/{}".format(settings.REMOTE_URL, self.image.url))
|
||||||
|
|
||||||
|
if self.image_annotations:
|
||||||
|
annotations.append(str(self.image_annotations))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
json = {
|
||||||
|
"images": images,
|
||||||
|
#"feedback_image": [],
|
||||||
|
"annotations": annotations,
|
||||||
|
"type": "anatomy",
|
||||||
|
"title": self.get_title(),
|
||||||
|
"question": str(self.question_type),
|
||||||
|
}
|
||||||
|
|
||||||
|
json["answers"] = list(self.get_correct_unstripped_answers())
|
||||||
|
|
||||||
|
return json
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class Answer(models.Model):
|
class Answer(models.Model):
|
||||||
@@ -313,7 +356,7 @@ class Exam(ExamBase):
|
|||||||
exam_order.append(q.id)
|
exam_order.append(q.id)
|
||||||
|
|
||||||
exam_questions[q.id] = {
|
exam_questions[q.id] = {
|
||||||
"title": "{}".format(q.description),
|
"title": q.get_title(),
|
||||||
"question": str(q.question_type),
|
"question": str(q.question_type),
|
||||||
"images": [image_as_base64(q.image)],
|
"images": [image_as_base64(q.image)],
|
||||||
"annotations": [str(q.image_annotations)],
|
"annotations": [str(q.image_annotations)],
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
{% extends 'anatomy/base.html' %}
|
{% extends 'anatomy/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="question-display-block">
|
||||||
|
Question <span class="question-number">1</span>
|
||||||
|
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
|
||||||
|
</div>
|
||||||
|
<div class="answers">Answers: </div>
|
||||||
|
</div>
|
||||||
<div class="anatomy">
|
<div class="anatomy">
|
||||||
<h2>Exam: {{ exam.name }}</h2>
|
<h2>Exam: {{ exam.name }}</h2>
|
||||||
<h3>Candidate: {{ cid }}</h3>
|
<h3>Candidate: {{ cid }}</h3>
|
||||||
Answers:
|
Answers:
|
||||||
<ul>{% for ans, score, correct_answer in answers_and_marks %}
|
<ul class="score-answer-list">{% for ans, score, correct_answer in answers_and_marks %}
|
||||||
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li>
|
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span
|
||||||
<span class="user-answer-score user-answer-score-{{score}}"><pre>{{ans}}</pre> ({{score}})</span>
|
class="correct-answer">{{ correct_answer }}</span></li>
|
||||||
|
<span class="user-answer-score user-answer-score-{{score}} rapid-ans">
|
||||||
|
<pre>{{ans}}</pre> ({{score}})
|
||||||
|
</span>
|
||||||
|
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||||
@@ -16,3 +26,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$(".view-question-link").on("click", function (e) {
|
||||||
|
console.log("click", e)
|
||||||
|
question_number = e.currentTarget.dataset.qn;
|
||||||
|
$("#single-dicom-viewer").empty();
|
||||||
|
$.ajax({
|
||||||
|
url: `{% url 'anatomy:question_review' pk=exam.pk %}`,
|
||||||
|
data: {
|
||||||
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
|
question_number: question_number
|
||||||
|
},
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
|
.done(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
let event = new CustomEvent('loadDicomViewerUrls', {
|
||||||
|
"detail": {"images" : data.images, "annotations" : data.annotations}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
$(".question-display-block .answers").empty().append(data.answers.toString());
|
||||||
|
n = parseInt(question_number) + 1
|
||||||
|
$(".question-display-block .question-number").empty().append(n);
|
||||||
|
// show some message according to the response.
|
||||||
|
// For eg. A message box showing that the status has been changed
|
||||||
|
})
|
||||||
|
.always(function () {
|
||||||
|
console.log('[Done]');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$(".view-question-link").first().click();
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
+101
-34
@@ -7,29 +7,82 @@ app_name = "anatomy"
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('', views.question_list, name='question_list'),
|
# path('', views.question_list, name='question_list'),
|
||||||
path("", views.AnatomyExamViews.index, name="index"),
|
path("", views.AnatomyExamViews.index, name="index"),
|
||||||
path("question/", cache_page(60 * 1)(views.AnatomyQuestionView.as_view()), name="anatomy_question_view"),
|
path(
|
||||||
|
"question/",
|
||||||
|
cache_page(60 * 1)(views.AnatomyQuestionView.as_view()),
|
||||||
|
name="anatomy_question_view",
|
||||||
|
),
|
||||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||||
path("question/create/", views.AnatomyQuestionCreate.as_view(), name="anatomy_question_create"),
|
path(
|
||||||
path("question/<int:pk>/update", views.AnatomyQuestionUpdate.as_view(), name="anatomy_question_update"),
|
"question/create/",
|
||||||
path("question/<int:pk>/save_annotation", views.question_save_annotation, name="question_save_annotation"),
|
views.AnatomyQuestionCreate.as_view(),
|
||||||
path("question/<int:pk>/answer/",
|
name="anatomy_question_create",
|
||||||
views.answer_question,
|
),
|
||||||
name="answer_question"),
|
path(
|
||||||
path("question/<int:pk>/clone", views.QuestionClone.as_view(), name="question_clone"),
|
"question/<int:pk>/update",
|
||||||
path("question/<int:pk>/delete", views.QuestionDelete.as_view(), name="question_delete"),
|
views.AnatomyQuestionUpdate.as_view(),
|
||||||
|
name="anatomy_question_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/save_annotation",
|
||||||
|
views.question_save_annotation,
|
||||||
|
name="question_save_annotation",
|
||||||
|
),
|
||||||
|
path("question/<int:pk>/answer/", views.answer_question, name="answer_question"),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/clone", views.QuestionClone.as_view(), name="question_clone"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/delete",
|
||||||
|
views.QuestionDelete.as_view(),
|
||||||
|
name="question_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/review",
|
||||||
|
views.question_review,
|
||||||
|
name="question_review",
|
||||||
|
),
|
||||||
path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
||||||
path("exam/<int:pk>/mark", views.AnatomyExamViews.mark_overview, name="mark_overview"),
|
path(
|
||||||
|
"exam/<int:pk>/mark", views.AnatomyExamViews.mark_overview, name="mark_overview"
|
||||||
|
),
|
||||||
path("exam/<int:pk>/<int:sk>/", views.exam_take, name="exam_take"),
|
path("exam/<int:pk>/<int:sk>/", views.exam_take, name="exam_take"),
|
||||||
path("exam/<int:pk>/question/<int:sk>/", views.AnatomyExamViews.exam_question_detail, name="exam_question_detail"),
|
path(
|
||||||
|
"exam/<int:pk>/question/<int:sk>/",
|
||||||
|
views.AnatomyExamViews.exam_question_detail,
|
||||||
|
name="exam_question_detail",
|
||||||
|
),
|
||||||
path("exam/<int:pk>/", views.AnatomyExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.AnatomyExamViews.exam_overview, name="exam_overview"),
|
||||||
path("exam/<int:pk>/json_edit", views.AnatomyExamViews.exam_json_edit, name="exam_json_edit"),
|
path(
|
||||||
path("exam/<int:pk>/scores", cache_page(60 * 1)(views.exam_scores_cid),
|
"exam/<int:pk>/json_edit",
|
||||||
name="exam_scores_cid"),
|
views.AnatomyExamViews.exam_json_edit,
|
||||||
path("exam/<int:pk>/scores/<int:sk>/", views.exam_scores_cid_user,
|
name="exam_json_edit",
|
||||||
name="exam_scores_cid_user"),
|
),
|
||||||
path("exam/<int:pk>/toggle_active", views.AnatomyExamViews.exam_toggle_active, name="exam_toggle_active"),
|
path(
|
||||||
path("exam/<int:pk>/toggle_results_published", views.AnatomyExamViews.exam_toggle_results_published, name="exam_toggle_results_published"),
|
"exam/<int:pk>/scores",
|
||||||
path("exam/submit", views.AnatomyExamViews.postExamAnswers, name="exam_answers_submit"),
|
cache_page(60 * 1)(views.exam_scores_cid),
|
||||||
|
name="exam_scores_cid",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/scores/<int:sk>/",
|
||||||
|
views.exam_scores_cid_user,
|
||||||
|
name="exam_scores_cid_user",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/toggle_active",
|
||||||
|
views.AnatomyExamViews.exam_toggle_active,
|
||||||
|
name="exam_toggle_active",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/toggle_results_published",
|
||||||
|
views.AnatomyExamViews.exam_toggle_results_published,
|
||||||
|
name="exam_toggle_results_published",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/submit",
|
||||||
|
views.AnatomyExamViews.postExamAnswers,
|
||||||
|
name="exam_answers_submit",
|
||||||
|
),
|
||||||
path("exam/", views.AnatomyExamViews.exam_list, name="exam_list"),
|
path("exam/", views.AnatomyExamViews.exam_list, name="exam_list"),
|
||||||
path("exam/all", views.AnatomyExamViews.exam_list_all, name="exam_list_all"),
|
path("exam/all", views.AnatomyExamViews.exam_list_all, name="exam_list_all"),
|
||||||
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
|
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
|
||||||
@@ -37,25 +90,39 @@ urlpatterns = [
|
|||||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||||
path("exam/json/", views.AnatomyExamViews.active_exams, name="active_exams"),
|
path("exam/json/", views.AnatomyExamViews.active_exams, name="active_exams"),
|
||||||
path("exam/json/<int:pk>", views.AnatomyExamViews.exam_json, name="exam_json"),
|
path("exam/json/<int:pk>", views.AnatomyExamViews.exam_json, name="exam_json"),
|
||||||
path("exam/json/<int:pk>/recreate", views.AnatomyExamViews.exam_json_recreate, name="exam_json_recreate"),
|
path(
|
||||||
|
"exam/json/<int:pk>/recreate",
|
||||||
|
views.AnatomyExamViews.exam_json_recreate,
|
||||||
|
name="exam_json_recreate",
|
||||||
|
),
|
||||||
# path("cid/", views.cid_selector, name="cid_selector"),
|
# path("cid/", views.cid_selector, name="cid_selector"),
|
||||||
path("ajax/exam/flag/", views.flag_question, name="flag_question"),
|
path("ajax/exam/flag/", views.flag_question, name="flag_question"),
|
||||||
path("body_part/create/", views.create_body_part, name="create_body_part"),
|
path("body_part/create/", views.create_body_part, name="create_body_part"),
|
||||||
path("body_part/ajax/get_body_part_id",
|
path(
|
||||||
|
"body_part/ajax/get_body_part_id",
|
||||||
views.get_body_part_id,
|
views.get_body_part_id,
|
||||||
name="get_body_part_id"),
|
name="get_body_part_id",
|
||||||
path("structure/create/",
|
),
|
||||||
views.create_structure,
|
path("structure/create/", views.create_structure, name="create_structure"),
|
||||||
name="create_structure"),
|
path(
|
||||||
path("structure/ajax/get_structure_id",
|
"structure/ajax/get_structure_id",
|
||||||
views.get_structure_id,
|
views.get_structure_id,
|
||||||
name="get_structure_id"),
|
name="get_structure_id",
|
||||||
path("examination/create/",
|
),
|
||||||
views.create_examination,
|
path("examination/create/", views.create_examination, name="create_examination"),
|
||||||
name="create_examination"),
|
path(
|
||||||
path("examination/ajax/get_examination_id",
|
"examination/ajax/get_examination_id",
|
||||||
views.get_examination_id,
|
views.get_examination_id,
|
||||||
name="get_examination_id"),
|
name="get_examination_id",
|
||||||
path("user_answers/", views.AnatomyUserAnswerView.as_view(), name="anatomy_user_answer_view"),
|
),
|
||||||
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="user_answer_delete"),
|
path(
|
||||||
|
"user_answers/",
|
||||||
|
views.AnatomyUserAnswerView.as_view(),
|
||||||
|
name="anatomy_user_answer_view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/<int:pk>/delete",
|
||||||
|
views.UserAnswerDelete.as_view(),
|
||||||
|
name="user_answer_delete",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1032,3 +1032,22 @@ class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
|||||||
model = Exam
|
model = Exam
|
||||||
template_name = "exam_confirm_delete.html"
|
template_name = "exam_confirm_delete.html"
|
||||||
success_url = reverse_lazy("anatomy:index")
|
success_url = reverse_lazy("anatomy:index")
|
||||||
|
|
||||||
|
def question_review(request, pk):
|
||||||
|
"""
|
||||||
|
Return a json representation of the question when the exam is active
|
||||||
|
"""
|
||||||
|
if request.is_ajax():
|
||||||
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
|
print(request.POST["question_number"])
|
||||||
|
print(type(request.POST["question_number"]))
|
||||||
|
|
||||||
|
if not exam.publish_results:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
question = exam.exam_questions.all()[int(request.POST["question_number"])]
|
||||||
|
|
||||||
|
question_json = question.get_question_json(based=False)
|
||||||
|
return JsonResponse(question_json)
|
||||||
|
return JsonResponse({"status": "error"})
|
||||||
+10
-2
@@ -17,7 +17,11 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"question/<int:pk>/", views.question_detail, name="question_detail"
|
"question/<int:pk>/", views.question_detail, name="question_detail"
|
||||||
), # path("question/<int:pk>/json", views.question_json, name="question_json"),
|
), # path("question/<int:pk>/json", views.question_json, name="question_json"),
|
||||||
path("question/<int:pk>/save_annotation", views.question_save_annotation, name="question_save_annotation"),
|
path(
|
||||||
|
"question/<int:pk>/save_annotation",
|
||||||
|
views.question_save_annotation,
|
||||||
|
name="question_save_annotation",
|
||||||
|
),
|
||||||
path("question/<int:pk>/split", views.rapid_split, name="rapid_split"),
|
path("question/<int:pk>/split", views.rapid_split, name="rapid_split"),
|
||||||
path("question/<int:pk>/clone", views.RapidClone.as_view(), name="rapid_clone"),
|
path("question/<int:pk>/clone", views.RapidClone.as_view(), name="rapid_clone"),
|
||||||
# path("verified/", views.verified, name="verified"),
|
# path("verified/", views.verified, name="verified"),
|
||||||
@@ -54,7 +58,11 @@ urlpatterns = [
|
|||||||
views.RapidExamViews.exam_json_edit,
|
views.RapidExamViews.exam_json_edit,
|
||||||
name="exam_json_edit",
|
name="exam_json_edit",
|
||||||
),
|
),
|
||||||
path("exam/<int:pk>/scores", cache_page(60 * 1)(views.exam_scores_cid), name="exam_scores_cid"),
|
path(
|
||||||
|
"exam/<int:pk>/scores",
|
||||||
|
cache_page(60 * 1)(views.exam_scores_cid),
|
||||||
|
name="exam_scores_cid",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores/<int:cid>/",
|
"exam/<int:pk>/scores/<int:cid>/",
|
||||||
views.exam_scores_cid_user,
|
views.exam_scores_cid_user,
|
||||||
|
|||||||
Reference in New Issue
Block a user