Merge ssh://penracourses.org.uk:/home/django/rad
This commit is contained in:
+1
-1
@@ -203,4 +203,4 @@ class BodyPartForm(ModelForm):
|
|||||||
class ExamForm(ModelForm):
|
class ExamForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Exam
|
model = Exam
|
||||||
fields = ["name", "time_limit", "exam_mode", "active"]
|
fields = ["name", "time_limit", "exam_mode", "active", "archive"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-08-16 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0043_rename_json_creation_id_exam_exam_json_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='archive',
|
||||||
|
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exam',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -22,7 +22,7 @@ a, a:link {
|
|||||||
.answer-list .answer:hover{
|
.answer-list .answer:hover{
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(167, 167, 167, 0.7);
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-list .correct {
|
.answer-list .correct {
|
||||||
@@ -34,6 +34,9 @@ a, a:link {
|
|||||||
content: " [Score = 2]";
|
content: " [Score = 2]";
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
.answer-list.rapid .correct::after{
|
||||||
|
content: " [Score = 1]";
|
||||||
|
}
|
||||||
|
|
||||||
.answer-list .half-correct {
|
.answer-list .half-correct {
|
||||||
color: yellow;
|
color: yellow;
|
||||||
@@ -43,6 +46,9 @@ a, a:link {
|
|||||||
content: " [Score = 1]";
|
content: " [Score = 1]";
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
.answer-list.rapid .half-correct::after{
|
||||||
|
content: " [Score = 0.5]";
|
||||||
|
}
|
||||||
|
|
||||||
.answer-list .incorrect {
|
.answer-list .incorrect {
|
||||||
color: red;
|
color: red;
|
||||||
@@ -507,6 +513,7 @@ td.user-answer-score-2::after {
|
|||||||
border: 1px dotted gray;
|
border: 1px dotted gray;
|
||||||
opacity: 20%;
|
opacity: 20%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.published-icon:hover {
|
.published-icon:hover {
|
||||||
@@ -517,6 +524,7 @@ td.user-answer-score-2::after {
|
|||||||
border: 1px solid purple;
|
border: 1px solid purple;
|
||||||
color: purple;
|
color: purple;
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea, input {
|
textarea, input {
|
||||||
@@ -524,3 +532,15 @@ td.user-answer-score-2::after {
|
|||||||
background-color: #343a40;
|
background-color: #343a40;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exam-list li, #full-question-list li {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-list li .flex-col, #full-question-list li .flex-col {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#full-question-list li .flex-col-4 {
|
||||||
|
flex: 4;
|
||||||
|
}
|
||||||
+1
-1
@@ -58,7 +58,7 @@ class AnatomyQuestionTable(tables.Table):
|
|||||||
class AnatomyUserAnswerTable(tables.Table):
|
class AnatomyUserAnswerTable(tables.Table):
|
||||||
select = tables.CheckBoxColumn(accessor=("pk"))
|
select = tables.CheckBoxColumn(accessor=("pk"))
|
||||||
delete = tables.LinkColumn(
|
delete = tables.LinkColumn(
|
||||||
"anatomy:anatomy_user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
"anatomy:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
||||||
)
|
)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
|
|||||||
@@ -4,22 +4,22 @@
|
|||||||
<h1>Examinations</h1>
|
<h1>Examinations</h1>
|
||||||
<div class="anatomy">
|
<div class="anatomy">
|
||||||
Active exams:<br/>
|
Active exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if exam.active %}
|
{% if exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
|
<a href="{% url 'anatomy:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Inactive exams:<br/>
|
Inactive exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if not exam.active %}
|
{% if not exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
|
<a href="{% url 'anatomy:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
|
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
|
||||||
<a href="{% url 'anatomy:anatomy_question_update' question.id %}" title="Edit the Question">Edit</a> <a href="{% url 'admin:anatomy_anatomyquestion_change' question.id %}" title="Edit the Question using the admin interface">Admin Edit</a>
|
<a href="{% url 'anatomy:question_detail' question.id %}" title="View the Question">View</a> <a href="{% url 'anatomy:anatomy_question_update' question.id %}" title="Edit the Question">Edit</a> <a href="{% url 'admin:anatomy_anatomyquestion_change' question.id %}" title="Edit the Question using the admin interface">Admin Edit</a>
|
||||||
<h3>{{ question.question_type }}</h3>
|
<h3>{{ question.question_type }}</h3>
|
||||||
|
|
||||||
<div id="single-dicom-viewer" class="marking-dicom" data-images="https://www.penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.get_annotations}}'>
|
<div id="single-dicom-viewer" class="marking-dicom" data-images="https://www.penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.get_annotations}}'>
|
||||||
|
|||||||
+2
-1
@@ -30,6 +30,7 @@ urlpatterns = [
|
|||||||
path("exam/<int:pk>/toggle_results_published", views.AnatomyExamViews.exam_toggle_results_published, name="exam_toggle_results_published"),
|
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/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/create", views.ExamCreate.as_view(), name="exam_create"),
|
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
|
||||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||||
@@ -55,5 +56,5 @@ urlpatterns = [
|
|||||||
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/", views.AnatomyUserAnswerView.as_view(), name="anatomy_user_answer_view"),
|
||||||
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="anatomy_user_answer_delete"),
|
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="user_answer_delete"),
|
||||||
]
|
]
|
||||||
|
|||||||
+3
-1
@@ -57,7 +57,7 @@ class ExamBase(models.Model):
|
|||||||
#exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
#exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
||||||
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
help_text="If an exam should be available", default=False
|
help_text="If an exam should be available to take", default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
exam_mode = models.BooleanField(
|
exam_mode = models.BooleanField(
|
||||||
@@ -75,6 +75,8 @@ class ExamBase(models.Model):
|
|||||||
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
||||||
exam_json_id = models.IntegerField(default=1, help_text="auto incrementing field when json recreated")
|
exam_json_id = models.IntegerField(default=1, help_text="auto incrementing field when json recreated")
|
||||||
|
|
||||||
|
archive = models.BooleanField(help_text="Archived exams will remain on the test system but will not be displayed by default", default=False)
|
||||||
|
|
||||||
#time_limit = models.IntegerField(
|
#time_limit = models.IntegerField(
|
||||||
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||||
# default=2100,
|
# default=2100,
|
||||||
|
|||||||
+10
-3
@@ -236,8 +236,15 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
return redirect("{}:question_detail".format(self.app_name), pk=pk)
|
return redirect("{}:question_detail".format(self.app_name), pk=pk)
|
||||||
|
|
||||||
@method_decorator(login_required)
|
@method_decorator(login_required)
|
||||||
def exam_list(self, request):
|
def exam_list_all(self, request):
|
||||||
exams = self.Exam.objects.filter(exam_mode=True)
|
return self.exam_list(request, all=True)
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def exam_list(self, request, all=False):
|
||||||
|
if all:
|
||||||
|
exams = self.Exam.objects.all().order_by("name")
|
||||||
|
else:
|
||||||
|
exams = self.Exam.objects.filter(exam_mode=True, archive=False).order_by("name")
|
||||||
|
|
||||||
if not self.check_user_access(request.user):
|
if not self.check_user_access(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
@@ -386,7 +393,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def active_exams(self, request, json=True, based=True):
|
def active_exams(self, request, json=True, based=True):
|
||||||
exams = self.Exam.objects.all()
|
exams = self.Exam.objects.filter(archive=False)
|
||||||
|
|
||||||
active_exams = {"exams": []}
|
active_exams = {"exams": []}
|
||||||
|
|
||||||
|
|||||||
@@ -206,3 +206,8 @@ LongSeriesImageFormSet = inlineformset_factory(
|
|||||||
max_num=2000,
|
max_num=2000,
|
||||||
field_classes="testing",
|
field_classes="testing",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ExamForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Exam
|
||||||
|
fields = ["name", "time_limit", "exam_mode", "active", "archive"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-08-16 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('longs', '0043_rename_sorted_series_long_series'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='archive',
|
||||||
|
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exam',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -23,6 +23,9 @@ Longs:
|
|||||||
<a href="{% url 'longs:long_create' %}" title="Create a new long case">Create Case</a> /
|
<a href="{% url 'longs:long_create' %}" title="Create a new long case">Create Case</a> /
|
||||||
<a href="{% url 'longs:long_series_create' %}" title="Create a new image series">Create Series</a>
|
<a href="{% url 'longs:long_series_create' %}" title="Create a new image series">Create Series</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
/ <a href="{% url 'longs:user_answer_table_view' %}" title="User answers">Answers</a>
|
||||||
|
{% endif %}
|
||||||
{% comment %} </br>
|
{% comment %} </br>
|
||||||
Questions by:
|
Questions by:
|
||||||
<span id="authors-link"><a href="{% url 'longs:author_list' %}">author</a></span> {% endcomment %}
|
<span id="authors-link"><a href="{% url 'longs:author_list' %}">author</a></span> {% endcomment %}
|
||||||
|
|||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends "longs/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Add Exam</h2>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -4,22 +4,22 @@
|
|||||||
<h1>Examinations</h1>
|
<h1>Examinations</h1>
|
||||||
<div class="longs">
|
<div class="longs">
|
||||||
Active exams:<br/>
|
Active exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if exam.active %}
|
{% if exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'longs:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'longs:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
|
<a href="{% url 'longs:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'longs:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Inactive exams:<br/>
|
Inactive exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if not exam.active %}
|
{% if not exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'longs:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'longs:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
|
<a href="{% url 'longs:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'longs:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="longs">
|
<div class="longs">
|
||||||
|
<a href="{% url 'longs:exam_update' exam.id %}" title="Edit the Exam">Edit</a>
|
||||||
|
<a href="{% url 'longs:exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
|
||||||
<a href="{% url 'admin:longs_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
|
<a href="{% url 'admin:longs_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
|
||||||
<h1>Exam: {{ exam.name }}</h1>
|
<h1>Exam: {{ exam.name }}</h1>
|
||||||
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.<br />
|
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.<br />
|
||||||
|
|||||||
@@ -13,44 +13,9 @@
|
|||||||
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button id="delete-all-button">Delete all</button>
|
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
|
||||||
$(document).ready(function () {
|
|
||||||
//$("thead input:checked").each((n, el) => {
|
|
||||||
|
|
||||||
answer_ids = [];
|
|
||||||
$("tbody input:checked").each((n, el) => {
|
|
||||||
answer_ids.push(el.value);
|
|
||||||
})
|
|
||||||
$("#delete-all-button").on("click", function () {
|
|
||||||
$.ajax({
|
|
||||||
url: "{% url 'longs:user_answer_delete_multiple' %}",
|
|
||||||
data: {
|
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
|
||||||
answer_ids: JSON.stringify(answer_ids) // true if checked else false
|
|
||||||
},
|
|
||||||
type: "POST",
|
|
||||||
dataType: "json",
|
|
||||||
})
|
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
|
||||||
.done(function (data) {
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if (data.status == "success") {
|
|
||||||
toastr.info('Deleted.')
|
|
||||||
}
|
|
||||||
// show some message according to the response.
|
|
||||||
// For eg. A message box showing that the status has been changed
|
|
||||||
})
|
|
||||||
.always(function () {
|
|
||||||
console.log('[Done]');
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'longs/base.html' %}
|
||||||
|
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="view-filter-options">
|
||||||
|
<h3>Filter User Answers</h3>
|
||||||
|
<form action="" method="get">
|
||||||
|
{{ filter.form }}
|
||||||
|
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% render_table table %}
|
||||||
|
<button id="delete-selected-button">Delete selected answers</button>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("table thead th input").click((e) => {
|
||||||
|
let status = e.currentTarget.checked; console.log(status, $("table tbody input")); $("table tbody input").prop("checked", status);
|
||||||
|
})
|
||||||
|
//$("thead input:checked").each((n, el) => {
|
||||||
|
|
||||||
|
$("#delete-selected-button").on("click", function () {
|
||||||
|
answer_ids = [];
|
||||||
|
$("tbody input:checked").each((n, el) => {
|
||||||
|
answer_ids.push(el.value);
|
||||||
|
})
|
||||||
|
if (confirm(`Delete ${answer_ids.length} answers?`)) {
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'longs:user_answer_delete_multiple' %}",
|
||||||
|
data: {
|
||||||
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
|
answer_ids: JSON.stringify(answer_ids) // true if checked else false
|
||||||
|
},
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
|
.done(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
if (data.status == "success") {
|
||||||
|
toastr.info('Deleted.')
|
||||||
|
}
|
||||||
|
// show some message according to the response.
|
||||||
|
// For eg. A message box showing that the status has been changed
|
||||||
|
})
|
||||||
|
.always(function () {
|
||||||
|
console.log('[Done]');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
+90
-18
@@ -11,18 +11,50 @@ urlpatterns = [
|
|||||||
path("question/", views.LongView.as_view(), name="long_view"),
|
path("question/", views.LongView.as_view(), name="long_view"),
|
||||||
path("series/", views.LongSeriesView.as_view(), name="long_series_view"),
|
path("series/", views.LongSeriesView.as_view(), name="long_series_view"),
|
||||||
path("series/<int:pk>", views.long_series_detail, name="long_series_detail"),
|
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(
|
||||||
path("series/<int:pk>/download", views.SeriesImagesZipView.as_view(), name="long_series_download"),
|
"series/<int:pk>/order_dicom",
|
||||||
path("series/<int:pk>/order_dicom_instance", views.long_series_order_dicom_instance, name="long_series_order_dicom_instance"),
|
views.long_series_order_dicom,
|
||||||
path("series/<int:pk>/order_dicom_SeriesInstanceUID", views.long_series_order_dicom_SeriesInstanceUID, name="long_series_order_dicom_SeriesInstanceUID"),
|
name="long_series_order_dicom",
|
||||||
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(
|
||||||
|
"series/<int:pk>/download",
|
||||||
|
views.SeriesImagesZipView.as_view(),
|
||||||
|
name="long_series_download",
|
||||||
|
),
|
||||||
|
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"),
|
# path("unchecked/", views.unchecked_list, name="unchecked_list"),
|
||||||
# path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
|
# path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
|
||||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
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>/json", views.question_json, name="question_json"),
|
||||||
path("question/<int:pk>/json/unbased", views.question_json_unbased, name="question_json_unbased"),
|
path(
|
||||||
path("question/<int:pk>/json/recreate", views.question_json_recreate, name="question_json_recreate"),
|
"question/<int:pk>/json/unbased",
|
||||||
|
views.question_json_unbased,
|
||||||
|
name="question_json_unbased",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/json/recreate",
|
||||||
|
views.question_json_recreate,
|
||||||
|
name="question_json_recreate",
|
||||||
|
),
|
||||||
path("question/<int:pk>/split", views.long_split, name="long_split"),
|
path("question/<int:pk>/split", views.long_split, name="long_split"),
|
||||||
path("question/<int:pk>/clone", views.LongClone.as_view(), name="long_clone"),
|
path("question/<int:pk>/clone", views.LongClone.as_view(), name="long_clone"),
|
||||||
# path("verified/", views.verified, name="verified"),
|
# path("verified/", views.verified, name="verified"),
|
||||||
@@ -39,8 +71,16 @@ urlpatterns = [
|
|||||||
name="exam_question_detail",
|
name="exam_question_detail",
|
||||||
),
|
),
|
||||||
path("exam/<int:pk>/", views.LongExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.LongExamViews.exam_overview, name="exam_overview"),
|
||||||
path("exam/<int:pk>/json_edit", views.LongExamViews.exam_json_edit, name="exam_json_edit"),
|
path(
|
||||||
path("exam/<int:pk>/question_json_refresh", views.refresh_exam_question_json, name="refresh_exam_question_json"),
|
"exam/<int:pk>/json_edit",
|
||||||
|
views.LongExamViews.exam_json_edit,
|
||||||
|
name="exam_json_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/question_json_refresh",
|
||||||
|
views.refresh_exam_question_json,
|
||||||
|
name="refresh_exam_question_json",
|
||||||
|
),
|
||||||
path("exam/<int:pk>/scores", views.exam_scores_cid, name="exam_scores_cid"),
|
path("exam/<int:pk>/scores", views.exam_scores_cid, name="exam_scores_cid"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores/<int:sk>/",
|
"exam/<int:pk>/scores/<int:sk>/",
|
||||||
@@ -57,13 +97,31 @@ urlpatterns = [
|
|||||||
views.LongExamViews.exam_toggle_results_published,
|
views.LongExamViews.exam_toggle_results_published,
|
||||||
name="exam_toggle_results_published",
|
name="exam_toggle_results_published",
|
||||||
),
|
),
|
||||||
path("exam/submit", views.LongExamViews.postExamAnswers, name="exam_answers_submit"),
|
path(
|
||||||
|
"exam/submit", views.LongExamViews.postExamAnswers, name="exam_answers_submit"
|
||||||
|
),
|
||||||
path("exam/", views.LongExamViews.exam_list, name="exam_list"),
|
path("exam/", views.LongExamViews.exam_list, name="exam_list"),
|
||||||
|
path("exam/all", views.LongExamViews.exam_list_all, name="exam_list_all"),
|
||||||
|
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
|
||||||
|
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||||
|
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||||
path("exam/json/", views.LongExamViews.active_exams, name="active_exams"),
|
path("exam/json/", views.LongExamViews.active_exams, name="active_exams"),
|
||||||
path("exam/json/<int:pk>", views.LongExamViews.exam_json, name="exam_json"),
|
path("exam/json/<int:pk>", views.LongExamViews.exam_json, name="exam_json"),
|
||||||
path("exam/json/<int:pk>/unbased", views.LongExamViews.exam_json_unbased, name="exam_json_unbased"),
|
path(
|
||||||
path("exam/json/<int:pk>/<int:sk>", views.LongExamViews.exam_question_json, name="exam_question_json"),
|
"exam/json/<int:pk>/unbased",
|
||||||
path("exam/json/<int:pk>/unbased/<int:sk>", views.LongExamViews.exam_question_json_unbased, name="exam_question_json_unbased"),
|
views.LongExamViews.exam_json_unbased,
|
||||||
|
name="exam_json_unbased",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/json/<int:pk>/<int:sk>",
|
||||||
|
views.LongExamViews.exam_question_json,
|
||||||
|
name="exam_question_json",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/json/<int:pk>/unbased/<int:sk>",
|
||||||
|
views.LongExamViews.exam_question_json_unbased,
|
||||||
|
name="exam_question_json_unbased",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"exam/json/<int:pk>/recreate",
|
"exam/json/<int:pk>/recreate",
|
||||||
views.LongExamViews.exam_json_recreate,
|
views.LongExamViews.exam_json_recreate,
|
||||||
@@ -106,8 +164,22 @@ urlpatterns = [
|
|||||||
name="long_series_update",
|
name="long_series_update",
|
||||||
),
|
),
|
||||||
path("<int:pk>/add_note", views.AddNote.as_view(), name="long_add_note"),
|
path("<int:pk>/add_note", views.AddNote.as_view(), name="long_add_note"),
|
||||||
path("user_answers/", views.UserAnswerTableView.as_view(), name="user_answer_table_view"),
|
path(
|
||||||
path("user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"),
|
"user_answers/",
|
||||||
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="user_answer_delete"),
|
views.UserAnswerTableView.as_view(),
|
||||||
path("user_answers/delete", views.user_answer_delete_multiple, name="user_answer_delete_multiple"),
|
name="user_answer_table_view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/<int:pk>/delete",
|
||||||
|
views.UserAnswerDelete.as_view(),
|
||||||
|
name="user_answer_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/delete",
|
||||||
|
views.user_answer_delete_multiple,
|
||||||
|
name="user_answer_delete_multiple",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+36
-3
@@ -27,6 +27,7 @@ from .forms import (
|
|||||||
SeriesFormSet,
|
SeriesFormSet,
|
||||||
NoteForm,
|
NoteForm,
|
||||||
ExaminationForm,
|
ExaminationForm,
|
||||||
|
ExamForm,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Long, LongSeries,
|
Long, LongSeries,
|
||||||
@@ -955,6 +956,38 @@ def long_series_order_upload_filename(request, pk):
|
|||||||
|
|
||||||
LongExamViews = ExamViews(Exam, Long, "longs", "long", loadJsonAnswer)
|
LongExamViews = ExamViews(Exam, Long, "longs", "long", loadJsonAnswer)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||||
|
model = Exam
|
||||||
|
form_class = ExamForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
form.instance.author.add(self.request.user.id)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamUpdate(
|
||||||
|
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
|
model = Exam
|
||||||
|
form_class = ExamForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
form.instance.author.add(self.request.user.id)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
|
model = Exam
|
||||||
|
success_url = reverse_lazy("longs:index")
|
||||||
|
|
||||||
|
|
||||||
class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||||
queryset = Exam.objects.all().order_by('name')
|
queryset = Exam.objects.all().order_by('name')
|
||||||
serializer_class = ExamSerializer
|
serializer_class = ExamSerializer
|
||||||
@@ -1014,7 +1047,7 @@ class UserAnswerView(LoginRequiredMixin, DetailView):
|
|||||||
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
table_class = UserAnswerTable
|
table_class = UserAnswerTable
|
||||||
template_name = "longs/question_view.html"
|
template_name = "longs/user_answer_question_view.html"
|
||||||
|
|
||||||
filterset_class = UserAnswerFilter
|
filterset_class = UserAnswerFilter
|
||||||
|
|
||||||
@@ -1031,8 +1064,8 @@ def user_answer_delete_multiple(request):
|
|||||||
# We could probably delete them all at once....
|
# We could probably delete them all at once....
|
||||||
for id in answer_ids:
|
for id in answer_ids:
|
||||||
CidUserAnswer.objects.get(pk=id).delete()
|
CidUserAnswer.objects.get(pk=id).delete()
|
||||||
return HttpResponseRedirect(request.path_info)
|
return JsonResponse({"status": "success"})
|
||||||
return HttpResponse("/")
|
return JsonResponse({"status": "error"})
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(lambda u: u.is_superuser)
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-08-16 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('physics', '0011_rename_json_creation_id_exam_exam_json_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='archive',
|
||||||
|
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exam',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,22 +4,22 @@
|
|||||||
<h1>Examinations</h1>
|
<h1>Examinations</h1>
|
||||||
<div class="physics">
|
<div class="physics">
|
||||||
Active exams:<br/>
|
Active exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if exam.active %}
|
{% if exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'physics:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}">(scores)</a> <span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span>
|
<a href="{% url 'physics:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Inactive exams:<br/>
|
Inactive exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if not exam.active %}
|
{% if not exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'physics:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}">(scores)</a> <span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span>
|
<a href="{% url 'physics:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+1
-1
@@ -221,4 +221,4 @@ AnswerUpdateFormSet = inlineformset_factory(
|
|||||||
class ExamForm(ModelForm):
|
class ExamForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Exam
|
model = Exam
|
||||||
fields = ["name", "time_limit", "exam_mode", "active"]
|
fields = ["name", "time_limit", "exam_mode", "active", "archive"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-08-16 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rapids', '0027_rename_json_creation_id_exam_exam_json_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='archive',
|
||||||
|
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exam',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+3
-3
@@ -550,7 +550,7 @@ class CidUserAnswer(models.Model):
|
|||||||
return 0
|
return 0
|
||||||
# If both are normal full marks
|
# If both are normal full marks
|
||||||
elif q.normal and self.normal:
|
elif q.normal and self.normal:
|
||||||
return 2
|
return 1
|
||||||
|
|
||||||
# Then compare answer strings (as per anatomy questions)
|
# Then compare answer strings (as per anatomy questions)
|
||||||
ans = self.answer_compare
|
ans = self.answer_compare
|
||||||
@@ -560,14 +560,14 @@ class CidUserAnswer(models.Model):
|
|||||||
).first()
|
).first()
|
||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
mark = 2
|
mark = 1
|
||||||
elif (
|
elif (
|
||||||
q.answers.filter(
|
q.answers.filter(
|
||||||
answer_compare__iexact=ans, status=Answer.MarkOptions.HALF_MARK
|
answer_compare__iexact=ans, status=Answer.MarkOptions.HALF_MARK
|
||||||
).first()
|
).first()
|
||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
mark = 1
|
mark = 0.5
|
||||||
elif q.answers.filter(
|
elif q.answers.filter(
|
||||||
answer_compare__iexact=ans, status=Answer.MarkOptions.INCORRECT
|
answer_compare__iexact=ans, status=Answer.MarkOptions.INCORRECT
|
||||||
).first():
|
).first():
|
||||||
|
|||||||
+2
-2
@@ -60,9 +60,9 @@ class RapidTable(tables.Table):
|
|||||||
class RapidUserAnswerTable(tables.Table):
|
class RapidUserAnswerTable(tables.Table):
|
||||||
select = tables.CheckBoxColumn(accessor=("pk"))
|
select = tables.CheckBoxColumn(accessor=("pk"))
|
||||||
delete = tables.LinkColumn(
|
delete = tables.LinkColumn(
|
||||||
"rapids:rapid_user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
"rapids:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
||||||
)
|
)
|
||||||
view = tables.LinkColumn('rapids:rapid_user_answer_view',
|
view = tables.LinkColumn('rapids:user_answer_view',
|
||||||
text='View',
|
text='View',
|
||||||
args=[A('pk')],
|
args=[A('pk')],
|
||||||
orderable=False)
|
orderable=False)
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ Rapids:
|
|||||||
<a href="{% url 'rapids:rapid_view' %}">Questions</a> /
|
<a href="{% url 'rapids:rapid_view' %}">Questions</a> /
|
||||||
<a href="{% url 'rapids:rapid_create' %}" title="Create a new question">Create Question</a>
|
<a href="{% url 'rapids:rapid_create' %}" title="Create a new question">Create Question</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
/ <a href="{% url 'rapids:user_answer_table_view' %}" title="User answers">Answers</a>
|
||||||
|
{% endif %}
|
||||||
{% comment %} </br>
|
{% comment %} </br>
|
||||||
Questions by:
|
Questions by:
|
||||||
<span id="authors-link"><a href="{% url 'rapids:author_list' %}">author</a></span> {% endcomment %}
|
<span id="authors-link"><a href="{% url 'rapids:author_list' %}">author</a></span> {% endcomment %}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||||
|
<input type="submit" value="Confirm">
|
||||||
|
</form>
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
{% extends 'rapids/base.html' %}
|
{% extends 'rapids/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ciduseranswer.question}}
|
CID: {{ciduseranswer.cid}}<br/>
|
||||||
{{ciduseranswer.normal}}
|
{{ciduseranswer.question}}<br/>
|
||||||
{{ciduseranswer.cid}}
|
Normal: {{ciduseranswer.normal}}<br/>
|
||||||
|
Answer: {{ciduseranswer.answer}}<br/>
|
||||||
|
<a href="{% url 'rapids:user_answer_delete' ciduseranswer.id %}">Delete answer</a>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -4,22 +4,22 @@
|
|||||||
<h1>Examinations</h1>
|
<h1>Examinations</h1>
|
||||||
<div class="rapids">
|
<div class="rapids">
|
||||||
Active exams:<br/>
|
Active exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if exam.active %}
|
{% if exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">(scores)</a> <span class="published-icon {{exam.publish_result}}">Published</span>
|
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Inactive exams:<br/>
|
Inactive exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if not exam.active %}
|
{% if not exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">(scores)</a> <span class="published-icon {{exam.publish_result}}">Published</span>
|
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -10,14 +10,20 @@
|
|||||||
Edit</a>
|
Edit</a>
|
||||||
<h1>Exam: {{ exam.name }}</h1>
|
<h1>Exam: {{ exam.name }}</h1>
|
||||||
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.<br />
|
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.<br />
|
||||||
Normal {{ exam.get_normal_abnormal_breakdown }}<br />
|
<div class="parent-help" title="">
|
||||||
Exam mode: {{ exam.exam_mode }}<br />
|
Normal count: {{ exam.get_normal_abnormal_breakdown }}<span
|
||||||
|
class="help-text">[Number of normal questions within the exam]</span>
|
||||||
|
</div>
|
||||||
|
<div class="parent-help" title="">
|
||||||
|
Exam mode: {{ exam.exam_mode }}<span
|
||||||
|
class="help-text">[When true the packet will be taken as an exam (it will not self mark and results will be saved here)]</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if exam.exam_mode %}
|
|
||||||
<div class="parent-help" title="Click to enable / disable the exam">
|
<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
|
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>
|
class="help-text">[When checked the exam will be available to take in the test system]</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% if exam.exam_mode %}
|
||||||
<div class="parent-help" title="Click to enable / disable the exam results">
|
<div class="parent-help" title="Click to enable / disable the exam results">
|
||||||
Publish results: <input type="checkbox" id="exam-publish-results-switch"
|
Publish results: <input type="checkbox" id="exam-publish-results-switch"
|
||||||
{% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will
|
{% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will
|
||||||
@@ -30,11 +36,14 @@
|
|||||||
{% for question in questions.all %}
|
{% for question in questions.all %}
|
||||||
|
|
||||||
<li data-question_pk={{question.pk}}>
|
<li data-question_pk={{question.pk}}>
|
||||||
|
<span class="flex-col">
|
||||||
<a href="{% url 'rapids:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">
|
<a href="{% url 'rapids:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">
|
||||||
{% for image in question.get_images %}
|
{% for image in question.get_images %}
|
||||||
<img src="{{ image|thumbnail_url:'exam-list' }}" alt="thumbail" />
|
<img src="{{ image|thumbnail_url:'exam-list' }}" alt="thumbail" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</a>
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="flex-col-4">
|
||||||
{% if not question.normal %}
|
{% if not question.normal %}
|
||||||
<b>Abnormality:</b> {{ question.get_abnormalities }} <b>Region:</b> {{ question.get_regions }}
|
<b>Abnormality:</b> {{ question.get_abnormalities }} <b>Region:</b> {{ question.get_regions }}
|
||||||
<br />
|
<br />
|
||||||
@@ -46,6 +55,7 @@
|
|||||||
Examination: {{ question.get_examinations }}, <a
|
Examination: {{ question.get_examinations }}, <a
|
||||||
href="{% url 'rapids:question_detail' pk=question.pk %}">View</a>, <a
|
href="{% url 'rapids:question_detail' pk=question.pk %}">View</a>, <a
|
||||||
href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
|
href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
|
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
|
||||||
<a href="{% url 'rapids:rapid_update' question.id %}" title="Edit the Question">Edit</a> <a
|
<a href="{% url 'rapids:question_detail' question.id %}" title="View the Question">View</a> <a href="{% url 'rapids:rapid_update' question.id %}" title="Edit the Question">Edit</a> <a
|
||||||
href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Question using the admin interface">Admin
|
href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Question using the admin interface">Admin
|
||||||
Edit</a>
|
Edit</a>
|
||||||
{% if question.normal %}
|
{% if question.normal %}
|
||||||
@@ -22,7 +22,7 @@ Region: {{ question.get_regions }}, Abnormalities: {{ question.get_abnormalities
|
|||||||
Click each answer to toggle through marks awarded (as per colour)
|
Click each answer to toggle through marks awarded (as per colour)
|
||||||
<div class="marking-list">
|
<div class="marking-list">
|
||||||
Unmarked:
|
Unmarked:
|
||||||
<ul id="new-answer-list" class="answer-list">
|
<ul id="new-answer-list" class="answer-list rapid">
|
||||||
{% for answer in user_answers %}
|
{% for answer in user_answers %}
|
||||||
<li>
|
<li>
|
||||||
<pre><span class="answer not-marked">{{ answer }}</span></pre>
|
<pre><span class="answer not-marked">{{ answer }}</span></pre>
|
||||||
@@ -30,7 +30,7 @@ Region: {{ question.get_regions }}, Abnormalities: {{ question.get_abnormalities
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
Marked:
|
Marked:
|
||||||
<ul id="marked-answer-list" class="answer-list">
|
<ul id="marked-answer-list" class="answer-list rapid">
|
||||||
{% for answer in correct_answers %}
|
{% for answer in correct_answers %}
|
||||||
<li>
|
<li>
|
||||||
<pre><span class="answer correct">{{ answer }}</span></pre>
|
<pre><span class="answer correct">{{ answer }}</span></pre>
|
||||||
@@ -47,7 +47,7 @@ Region: {{ question.get_regions }}, Abnormalities: {{ question.get_abnormalities
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
|
<div class="answer-list key">Key: <span class="correct">1 Marks</span>, <span class="half-correct">0.5
|
||||||
Mark</span>, <span class="incorrect">0 Marks</span></div>
|
Mark</span>, <span class="incorrect">0 Marks</span></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="{% url 'rapids:rapid_add_note' pk=question.pk %}"> Add Note</a>
|
<a href="{% url 'rapids:rapid_add_note' pk=question.pk %}"> Add Note</a>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<a href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Rapid using the admin interface">Admin Edit</a>
|
<a href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Rapid using the admin interface">Admin Edit</a>
|
||||||
|
<a href="{% url 'rapids:question_user_answers' question.id %}" title="View user answers associated with this question">User answers</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'rapids/question_display_block.html' %}
|
{% include 'rapids/question_display_block.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'rapids/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<ul>
|
||||||
|
{% for answer in answers %}
|
||||||
|
<li><a href="{% url 'rapids:user_answer_view' pk=answer.pk %}">{{answer}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{% extends 'rapids/base.html' %}
|
||||||
|
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="view-filter-options">
|
||||||
|
<h3>Rapid User Answers</h3>
|
||||||
|
<form action="" method="get">
|
||||||
|
{{ filter.form }}
|
||||||
|
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% render_table table %}
|
||||||
|
<button id="delete-selected-button">Delete selected answers</button>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("table thead th input").click((e) => {
|
||||||
|
let status = e.currentTarget.checked; console.log(status, $("table tbody input")); $("table tbody input").prop("checked", status);
|
||||||
|
})
|
||||||
|
//$("thead input:checked").each((n, el) => {
|
||||||
|
|
||||||
|
$("#delete-selected-button").on("click", function () {
|
||||||
|
answer_ids = [];
|
||||||
|
$("tbody input:checked").each((n, el) => {
|
||||||
|
answer_ids.push(el.value);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (confirm(`Delete ${answer_ids.length} answers?`)) {
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'rapids:user_answer_delete_multiple' %}",
|
||||||
|
data: {
|
||||||
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
|
answer_ids: JSON.stringify(answer_ids) // true if checked else false
|
||||||
|
},
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
|
.done(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
if (data.status == "success") {
|
||||||
|
toastr.info('Deleted.')
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
toastr.info('Error deleting questions.')
|
||||||
|
|
||||||
|
}
|
||||||
|
// show some message according to the response.
|
||||||
|
// For eg. A message box showing that the status has been changed
|
||||||
|
})
|
||||||
|
.always(function () {
|
||||||
|
console.log('[Done]');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
+86
-31
@@ -17,56 +17,111 @@ urlpatterns = [
|
|||||||
# path("verified/", views.verified, name="verified"),
|
# path("verified/", views.verified, name="verified"),
|
||||||
# path("all_questions/", views.all_questions, name="all_questions"),
|
# path("all_questions/", views.all_questions, name="all_questions"),
|
||||||
path("question/<int:pk>/scrap", views.rapid_scrap, name="rapid_scrap"),
|
path("question/<int:pk>/scrap", views.rapid_scrap, name="rapid_scrap"),
|
||||||
path("question/<int:pk>/delete", views.QuestionDelete.as_view(), name="question_delete"),
|
path(
|
||||||
|
"question/<int:pk>/delete",
|
||||||
|
views.QuestionDelete.as_view(),
|
||||||
|
name="question_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/user_answers",
|
||||||
|
views.question_user_answers,
|
||||||
|
name="question_user_answers",
|
||||||
|
),
|
||||||
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.RapidExamViews.mark_overview, name="mark_overview"),
|
path(
|
||||||
|
"exam/<int:pk>/mark", views.RapidExamViews.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.RapidExamViews.exam_question_detail, name="exam_question_detail"),
|
path(
|
||||||
|
"exam/<int:pk>/question/<int:sk>/",
|
||||||
|
views.RapidExamViews.exam_question_detail,
|
||||||
|
name="exam_question_detail",
|
||||||
|
),
|
||||||
path("exam/<int:pk>/", views.RapidExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.RapidExamViews.exam_overview, name="exam_overview"),
|
||||||
path("exam/<int:pk>/json_edit", views.RapidExamViews.exam_json_edit, name="exam_json_edit"),
|
path(
|
||||||
path("exam/<int:pk>/scores", views.exam_scores_cid,
|
"exam/<int:pk>/json_edit",
|
||||||
name="exam_scores_cid"),
|
views.RapidExamViews.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.RapidExamViews.exam_toggle_active, name="exam_toggle_active"),
|
path("exam/<int:pk>/scores", views.exam_scores_cid, name="exam_scores_cid"),
|
||||||
path("exam/<int:pk>/toggle_results_published", views.RapidExamViews.exam_toggle_results_published, name="exam_toggle_results_published"),
|
path(
|
||||||
path("exam/submit", views.RapidExamViews.postExamAnswers, name="exam_answers_submit"),
|
"exam/<int:pk>/scores/<int:sk>/",
|
||||||
|
views.exam_scores_cid_user,
|
||||||
|
name="exam_scores_cid_user",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/toggle_active",
|
||||||
|
views.RapidExamViews.exam_toggle_active,
|
||||||
|
name="exam_toggle_active",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/toggle_results_published",
|
||||||
|
views.RapidExamViews.exam_toggle_results_published,
|
||||||
|
name="exam_toggle_results_published",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/submit", views.RapidExamViews.postExamAnswers, name="exam_answers_submit"
|
||||||
|
),
|
||||||
path("exam/", views.RapidExamViews.exam_list, name="exam_list"),
|
path("exam/", views.RapidExamViews.exam_list, name="exam_list"),
|
||||||
|
path("exam/all", views.RapidExamViews.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"),
|
||||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||||
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.RapidExamViews.active_exams, name="active_exams"),
|
path("exam/json/", views.RapidExamViews.active_exams, name="active_exams"),
|
||||||
path("exam/json/<int:pk>", views.RapidExamViews.exam_json, name="exam_json"),
|
path("exam/json/<int:pk>", views.RapidExamViews.exam_json, name="exam_json"),
|
||||||
path("exam/json/<int:pk>/unbased", views.RapidExamViews.exam_json_unbased, name="exam_json_unbased"),
|
path(
|
||||||
path("exam/json/<int:pk>/recreate", views.RapidExamViews.exam_json_recreate, name="exam_json_recreate"),
|
"exam/json/<int:pk>/unbased",
|
||||||
|
views.RapidExamViews.exam_json_unbased,
|
||||||
|
name="exam_json_unbased",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"exam/json/<int:pk>/recreate",
|
||||||
|
views.RapidExamViews.exam_json_recreate,
|
||||||
|
name="exam_json_recreate",
|
||||||
|
),
|
||||||
path("create/", views.RapidCreate.as_view(), name="rapid_create"),
|
path("create/", views.RapidCreate.as_view(), name="rapid_create"),
|
||||||
path("create/exam/<int:pk>", views.RapidCreate.as_view(), name="rapid_create_exam"),
|
path("create/exam/<int:pk>", views.RapidCreate.as_view(), name="rapid_create_exam"),
|
||||||
path("create/defaults",
|
path(
|
||||||
|
"create/defaults",
|
||||||
views.RapidCreationDefaultView.as_view(),
|
views.RapidCreationDefaultView.as_view(),
|
||||||
name="rapid_create_defaults"),
|
name="rapid_create_defaults",
|
||||||
|
),
|
||||||
path("region/create/", views.create_region, name="create_region"),
|
path("region/create/", views.create_region, name="create_region"),
|
||||||
path("region/ajax/get_region_id",
|
path("region/ajax/get_region_id", views.get_region_id, name="get_region_id"),
|
||||||
views.get_region_id,
|
path("abnormality/create/", views.create_abnormality, name="create_abnormality"),
|
||||||
name="get_region_id"),
|
path(
|
||||||
path("abnormality/create/",
|
"abnormality/ajax/get_abnormality_id",
|
||||||
views.create_abnormality,
|
|
||||||
name="create_abnormality"),
|
|
||||||
path("abnormality/ajax/get_abnormality_id",
|
|
||||||
views.get_abnormality_id,
|
views.get_abnormality_id,
|
||||||
name="get_abnormality_id"),
|
name="get_abnormality_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(
|
path(
|
||||||
"question/<int:pk>/update",
|
"question/<int:pk>/update",
|
||||||
views.RapidUpdate.as_view(),
|
views.RapidUpdate.as_view(),
|
||||||
name="rapid_update",
|
name="rapid_update",
|
||||||
),
|
),
|
||||||
path("<int:pk>/add_note", views.AddNote.as_view(), name="rapid_add_note"),
|
path("<int:pk>/add_note", views.AddNote.as_view(), name="rapid_add_note"),
|
||||||
path("user_answers/", views.UserAnswerTableView.as_view(), name="rapid_user_answer_table_view"),
|
path(
|
||||||
path("user_answers/<int:pk>", views.UserAnswerView.as_view(), name="rapid_user_answer_view"),
|
"user_answers/",
|
||||||
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="rapid_user_answer_delete"),
|
views.UserAnswerTableView.as_view(),
|
||||||
|
name="user_answer_table_view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/<int:pk>/delete",
|
||||||
|
views.UserAnswerDelete.as_view(),
|
||||||
|
name="user_answer_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user_answers/delete",
|
||||||
|
views.user_answer_delete_multiple,
|
||||||
|
name="user_answer_delete_multiple",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+33
-9
@@ -126,6 +126,18 @@ def question_detail(request, pk):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def question_user_answers(request, pk):
|
||||||
|
rapid = get_object_or_404(Rapid, pk=pk)
|
||||||
|
|
||||||
|
answers = CidUserAnswer.objects.filter(question__id=rapid.pk)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"rapids/question_user_answers.html",
|
||||||
|
{"question": rapid, "answers": answers},
|
||||||
|
)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def rapid_split(request, pk):
|
def rapid_split(request, pk):
|
||||||
rapid = get_object_or_404(Rapid, pk=pk)
|
rapid = get_object_or_404(Rapid, pk=pk)
|
||||||
@@ -861,7 +873,7 @@ def exam_scores_cid(request, pk):
|
|||||||
)
|
)
|
||||||
fig_html = fig.to_html()
|
fig_html = fig.to_html()
|
||||||
|
|
||||||
max_score = len(questions) * 2
|
max_score = len(questions)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
@@ -933,7 +945,7 @@ def exam_scores_cid_user(request, pk, sk):
|
|||||||
else:
|
else:
|
||||||
total_score = sum(answers_marks)
|
total_score = sum(answers_marks)
|
||||||
|
|
||||||
max_score = len(questions) * 2
|
max_score = len(questions)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
@@ -985,6 +997,11 @@ class ExamUpdate(
|
|||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
|
model = Exam
|
||||||
|
success_url = reverse_lazy("rapids:index")
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
|
|
||||||
@@ -992,14 +1009,14 @@ class UserAnswerView(LoginRequiredMixin, DetailView):
|
|||||||
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
table_class = RapidUserAnswerTable
|
table_class = RapidUserAnswerTable
|
||||||
template_name = "rapids/rapid_question_view.html"
|
template_name = "rapids/user_answer_question_view.html"
|
||||||
|
|
||||||
filterset_class = RapidUserAnswerFilter
|
filterset_class = RapidUserAnswerFilter
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
|
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
success_url = reverse_lazy("rapids:rapid_user_answer_table_view")
|
success_url = reverse_lazy("rapids:user_answer_table_view")
|
||||||
|
|
||||||
|
|
||||||
class LargeResultsSetPagination(PageNumberPagination):
|
class LargeResultsSetPagination(PageNumberPagination):
|
||||||
@@ -1070,11 +1087,6 @@ class RapidLateralityViewSet(
|
|||||||
pagination_class = StandardResultsSetPagination
|
pagination_class = StandardResultsSetPagination
|
||||||
|
|
||||||
|
|
||||||
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
|
||||||
model = Exam
|
|
||||||
success_url = reverse_lazy("rapids:index")
|
|
||||||
|
|
||||||
|
|
||||||
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
|
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
|
||||||
# model = QuestionNote
|
# model = QuestionNote
|
||||||
# form_class = QuestionNoteForm
|
# form_class = QuestionNoteForm
|
||||||
@@ -1106,3 +1118,15 @@ class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
|||||||
# get_object_or_404(question, pk=pk)
|
# get_object_or_404(question, pk=pk)
|
||||||
# return { "content_type" : content_type,
|
# return { "content_type" : content_type,
|
||||||
# "object_id" : pk }
|
# "object_id" : pk }
|
||||||
|
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
def user_answer_delete_multiple(request):
|
||||||
|
if request.is_ajax():
|
||||||
|
print(request.POST["answer_ids"])
|
||||||
|
answer_ids = json.loads(request.POST["answer_ids"])
|
||||||
|
|
||||||
|
# We could probably delete them all at once....
|
||||||
|
for id in answer_ids:
|
||||||
|
CidUserAnswer.objects.get(pk=id).delete()
|
||||||
|
return JsonResponse({"status": "success"})
|
||||||
|
return JsonResponse({"status": "error"})
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-08-16 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sbas', '0003_rename_json_creation_id_exam_exam_json_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='archive',
|
||||||
|
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exam',
|
||||||
|
name='active',
|
||||||
|
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,22 +4,22 @@
|
|||||||
<h1>Examinations</h1>
|
<h1>Examinations</h1>
|
||||||
<div class="sbas">
|
<div class="sbas">
|
||||||
Active exams:<br/>
|
Active exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if exam.active %}
|
{% if exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}">(scores)</a>
|
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Inactive exams:<br/>
|
Inactive exams:<br/>
|
||||||
<ul>
|
<ul class="exam-list">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% if not exam.active %}
|
{% if not exam.active %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}">(scores)</a>
|
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+21
-1
@@ -22,7 +22,7 @@ a, a:link {
|
|||||||
.answer-list .answer:hover{
|
.answer-list .answer:hover{
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(167, 167, 167, 0.7);
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-list .correct {
|
.answer-list .correct {
|
||||||
@@ -34,6 +34,9 @@ a, a:link {
|
|||||||
content: " [Score = 2]";
|
content: " [Score = 2]";
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
.answer-list.rapid .correct::after{
|
||||||
|
content: " [Score = 1]";
|
||||||
|
}
|
||||||
|
|
||||||
.answer-list .half-correct {
|
.answer-list .half-correct {
|
||||||
color: yellow;
|
color: yellow;
|
||||||
@@ -43,6 +46,9 @@ a, a:link {
|
|||||||
content: " [Score = 1]";
|
content: " [Score = 1]";
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
.answer-list.rapid .half-correct::after{
|
||||||
|
content: " [Score = 0.5]";
|
||||||
|
}
|
||||||
|
|
||||||
.answer-list .incorrect {
|
.answer-list .incorrect {
|
||||||
color: red;
|
color: red;
|
||||||
@@ -507,6 +513,7 @@ td.user-answer-score-2::after {
|
|||||||
border: 1px dotted gray;
|
border: 1px dotted gray;
|
||||||
opacity: 20%;
|
opacity: 20%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.published-icon:hover {
|
.published-icon:hover {
|
||||||
@@ -517,6 +524,7 @@ td.user-answer-score-2::after {
|
|||||||
border: 1px solid purple;
|
border: 1px solid purple;
|
||||||
color: purple;
|
color: purple;
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea, input {
|
textarea, input {
|
||||||
@@ -524,3 +532,15 @@ td.user-answer-score-2::after {
|
|||||||
background-color: #343a40;
|
background-color: #343a40;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exam-list li, #full-question-list li {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-list li .flex-col, #full-question-list li .flex-col {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#full-question-list li .flex-col-4 {
|
||||||
|
flex: 4;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user