.
This commit is contained in:
+44
-1
@@ -213,6 +213,49 @@ class AnatomyQuestion(models.Model):
|
||||
def get_annotations(self):
|
||||
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
|
||||
class Answer(models.Model):
|
||||
@@ -313,7 +356,7 @@ class Exam(ExamBase):
|
||||
exam_order.append(q.id)
|
||||
|
||||
exam_questions[q.id] = {
|
||||
"title": "{}".format(q.description),
|
||||
"title": q.get_title(),
|
||||
"question": str(q.question_type),
|
||||
"images": [image_as_base64(q.image)],
|
||||
"annotations": [str(q.image_annotations)],
|
||||
|
||||
@@ -1,18 +1,69 @@
|
||||
{% extends 'anatomy/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="anatomy">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
Answers:
|
||||
<ul>{% 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>
|
||||
<span class="user-answer-score user-answer-score-{{score}}"><pre>{{ans}}</pre> ({{score}})</span>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||
<div>
|
||||
<a href="{% url 'cid_scores' cid %}">Other exams</a>
|
||||
<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>
|
||||
{% endblock %}
|
||||
<div class="anatomy">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
Answers:
|
||||
<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>
|
||||
<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 %}
|
||||
</ul>
|
||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||
<div>
|
||||
<a href="{% url 'cid_scores' cid %}">Other exams</a>
|
||||
</div>
|
||||
</div>
|
||||
{% 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 %}
|
||||
|
||||
+105
-38
@@ -7,29 +7,82 @@ app_name = "anatomy"
|
||||
urlpatterns = [
|
||||
# path('', views.question_list, name='question_list'),
|
||||
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/create/", views.AnatomyQuestionCreate.as_view(), name="anatomy_question_create"),
|
||||
path("question/<int:pk>/update", 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(
|
||||
"question/create/",
|
||||
views.AnatomyQuestionCreate.as_view(),
|
||||
name="anatomy_question_create",
|
||||
),
|
||||
path(
|
||||
"question/<int:pk>/update",
|
||||
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>/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>/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>/json_edit", views.AnatomyExamViews.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/<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/<int:pk>/json_edit",
|
||||
views.AnatomyExamViews.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/<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/all", views.AnatomyExamViews.exam_list_all, name="exam_list_all"),
|
||||
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/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>/recreate", views.AnatomyExamViews.exam_json_recreate, name="exam_json_recreate"),
|
||||
#path("cid/", views.cid_selector, name="cid_selector"),
|
||||
path(
|
||||
"exam/json/<int:pk>/recreate",
|
||||
views.AnatomyExamViews.exam_json_recreate,
|
||||
name="exam_json_recreate",
|
||||
),
|
||||
# path("cid/", views.cid_selector, name="cid_selector"),
|
||||
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/ajax/get_body_part_id",
|
||||
views.get_body_part_id,
|
||||
name="get_body_part_id"),
|
||||
path("structure/create/",
|
||||
views.create_structure,
|
||||
name="create_structure"),
|
||||
path("structure/ajax/get_structure_id",
|
||||
views.get_structure_id,
|
||||
name="get_structure_id"),
|
||||
path("examination/create/",
|
||||
views.create_examination,
|
||||
name="create_examination"),
|
||||
path("examination/ajax/get_examination_id",
|
||||
views.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(
|
||||
"body_part/ajax/get_body_part_id",
|
||||
views.get_body_part_id,
|
||||
name="get_body_part_id",
|
||||
),
|
||||
path("structure/create/", views.create_structure, name="create_structure"),
|
||||
path(
|
||||
"structure/ajax/get_structure_id",
|
||||
views.get_structure_id,
|
||||
name="get_structure_id",
|
||||
),
|
||||
path("examination/create/", views.create_examination, name="create_examination"),
|
||||
path(
|
||||
"examination/ajax/get_examination_id",
|
||||
views.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",
|
||||
),
|
||||
]
|
||||
|
||||
+20
-1
@@ -1031,4 +1031,23 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||
model = Exam
|
||||
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"})
|
||||
@@ -1,40 +1,40 @@
|
||||
{% extends 'rapids/base.html' %}
|
||||
|
||||
{% 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 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="answers">Answers: </div>
|
||||
</div>
|
||||
<div class="rapids">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
Answers:
|
||||
<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
|
||||
<div class="rapids">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
Answers:
|
||||
<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>
|
||||
<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>
|
||||
<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 %}
|
||||
</ul>
|
||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||
<br /> Normalised score: {{ normalised_score }}
|
||||
<div>
|
||||
<a href="{% url 'cid_scores' cid %}">Other exams</a>
|
||||
</ul>
|
||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||
<br /> Normalised score: {{ normalised_score }}
|
||||
<div>
|
||||
<a href="{% url 'cid_scores' cid %}">Other exams</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% 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({
|
||||
<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 'rapids:question_review' pk=exam.pk %}`,
|
||||
data: {
|
||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||
@@ -44,7 +44,7 @@
|
||||
dataType: "json",
|
||||
})
|
||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||
.done(function (data) {
|
||||
.done(function (data) {
|
||||
console.log(data);
|
||||
|
||||
let event = new CustomEvent('loadDicomViewerUrls', {
|
||||
@@ -55,16 +55,16 @@
|
||||
$(".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.
|
||||
// show some message according to the response.
|
||||
// For eg. A message box showing that the status has been changed
|
||||
})
|
||||
.always(function () {
|
||||
.always(function () {
|
||||
console.log('[Done]');
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
$(".view-question-link").first().click();
|
||||
$(".view-question-link").first().click();
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
+10
-2
@@ -17,7 +17,11 @@ urlpatterns = [
|
||||
path(
|
||||
"question/<int:pk>/", views.question_detail, name="question_detail"
|
||||
), # 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>/clone", views.RapidClone.as_view(), name="rapid_clone"),
|
||||
# path("verified/", views.verified, name="verified"),
|
||||
@@ -54,7 +58,11 @@ urlpatterns = [
|
||||
views.RapidExamViews.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(
|
||||
"exam/<int:pk>/scores/<int:cid>/",
|
||||
views.exam_scores_cid_user,
|
||||
|
||||
Reference in New Issue
Block a user