allow completion of sbas / physics exams

This commit is contained in:
Ross
2024-12-30 11:12:36 +00:00
parent 13713d870f
commit 7493d55bc2
17 changed files with 362 additions and 240 deletions
+9
View File
@@ -1518,6 +1518,12 @@ class CidUserExam(models.Model):
else:
return "CID" + str(self.cid_user.cid)
def complete_exam(self):
# TODO add examuserstatus?
self.end_time = timezone.now()
self.completed = True
self.save()
CID_GROUP_EXAMS = (
("SBAs", "sba_cid_user_groups"),
@@ -1729,7 +1735,10 @@ class ExamCollection(models.Model, AuthorMixin):
)
def __str__(self):
if self.date is not None and self.date is not "":
return f"{self.name} [{self.date}]"
else:
return f"{self.name}"
def get_absolute_url(self):
return reverse("generic:examcollection_detail", kwargs={"pk": self.pk})
@@ -2,7 +2,12 @@
{% block content %}
<h1>{{ object.name }} [{{object.date}}] </h1>
<h1>
{{ object.name }}
{% if object.date %}
[{{object.date}}]
{% endif %}
</h1>
<p>
<a href="{% url 'generic:examcollection_edit' object.pk %}">Edit</a>
+4 -2
View File
@@ -9,13 +9,14 @@
{% endif %}
{% if request.user.is_authenticated and valid_user %}
<a href="{% url 'physics:exam_take_user' pk=exam.pk sk=0 %}">Start Exam</a>
<a href="{% url 'physics:exam_take_user' pk=exam.pk sk=0 %}" title="Click to start the exam"><button class="btn btn-primary">Start Exam</button></a>
{% else %}
Enter your CID and passcode in the below boxes.<br />
<p><input id="cid-box" type="text" value="Candidate ID"></p>
<p><input id="passcode-box" type="text" value="Passcode"></p>
<button>Start exam</button>
<button type="button" class="btn btn-primary">Start exam</button>
<script type="text/javascript">
$(document).ready(function () {
@@ -53,4 +54,5 @@
});
</script>
{% endif %}
{% include "physics/exam_take_help.html" %}
{% endblock %}
+8 -13
View File
@@ -15,6 +15,10 @@
<div class="alert alert-primary review-mode-alert" role="alert">
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='physics' pk=question.pk %}')">here</a>.
</div>
{% elif cid_user_exam.completed %}
<div class="alert alert-primary review-mode-alert" role="alert">
Exam completed.
</div>
{% endif %}
<div>
@@ -92,24 +96,15 @@
<div id="menu-list">
</div>
<details><summary>Help</summary>
<p>Each question contains a list of 5 different statements. These are either True or False</p>
<p>Click on each statement to toggle between True and False. The current state can be seen to the right of the statement. </p>
<p>Your answers are saved when navigating between questions (or if the save button is clicked on the final question). An overview of all the questions can be seen by clicking on the overview button (or by clicking <a target="_blank" href="
{% if cid %}
{% url 'physics:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode %}
{% else %}
{% url 'physics:exam_scores_user' pk=exam.pk %}
{% endif %}
">here</a>).</p>
</details>
{% include "physics/exam_take_help.html" %}
{% endblock %}
{% block js %}
<script>
$(document).ready(() => {
/* beautify ignore:start */
{% if not exam.publish_results %}
{% if not exam.publish_results and not cid_user_exam.completed %}
let time_limit = '{{exam.time_limit}}'
if (time_limit != "None") {
@@ -117,7 +112,7 @@
initializeClock("clockdiv", end_time);
}
{% else %}
{% elif exam.publish_result %}
$(`ol.physics-answer-list input`).each((n, el) => { el.disabled = true; });
{{question.get_answers_js}}.forEach((el, n) => {
console.log(el)
@@ -0,0 +1,14 @@
<details class="help-text pt-3">
<summary><i class="bi bi-info-circle"></i> Help</summary>
<p>Each question contains a list of 5 different statements. These are either True or False</p>
<p>Click on each statement to toggle between True and False. The current state can be seen to the right of the statement. </p>
<p>Your answers are saved when navigating between questions (or if the save button is clicked on the final question). An overview of all the questions can be seen by clicking on the overview button.</p>
<p>Results are also available on the results page
<a target="_blank" href="
{% if cid %}
{% url 'physics:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode %}
{% else %}
{% url 'physics:exam_scores_user' pk=exam.pk %}
{% endif %}
">here</a>.</p>
</details>
@@ -2,7 +2,7 @@
{% block content %}
<span id="user-id">
{% if request.user.is_authenticated %}
{% if not cid %}
User: {{request.user}}
{% else %}
CID: {{cid}}
@@ -22,9 +22,16 @@
<a href="{% url 'physics:exam_scores_cid_user' pk=exam.id cid=cid passcode=passcode %}">here</a>
{% endif %}
</div>
{% endif %}
{% elif cid_user_exam.completed %}
<div class="alert alert-primary review-mode-alert" role="alert">
Exam completed. Answers are available
{% if request.user.is_authenticated %}
<a href="{% url 'physics:exam_scores_user' pk=exam.id %}">here</a>
{% if not exam.publish_results and answer_count != exam_length %}
{% else %}
<a href="{% url 'physics:exam_scores_cid_user' pk=exam.id cid=cid passcode=passcode %}">here</a>
{% endif %}
{% elif answer_count != exam_length %}
<div id="unanswered-questions-alert" class="alert alert-warning" role="alert">
You have unanswered questions.
</div>
@@ -34,7 +41,7 @@
<div class="overview-text">{{answer_count}} out of {{exam_length}} questions answered. Unanswered questions are shown in red. <p>Click to go to a question.</p></div>
<div class="physics-finish-list">
{% for question, answer in question_answer_tuples %}
{% if request.user.is_authenticated %}
{% if not cid %}
<a href="{% url 'physics:exam_take_user' pk=exam.id sk=forloop.counter0 %}"><button {% if not answer %}class="unanswered" title="You have not answered this question"{% endif %}>{{forloop.counter}}: {{answer.answer}}</button></a>
{% else %}
@@ -44,15 +51,26 @@
</div>
<div id="time-details">
Start time: {{cid_user_exam.start_time}}<br/>
Last change time: {{cid_user_exam.end_time}}
Last change time: {{cid_user_exam.end_time}}<br/>
Completed: {{cid_user_exam.completed}}
</div>
{% if not cid_user_exam.completed %}
{% if not cid %}
<button hx-get="{% url 'physics:exam_complete_user' pk=exam.id %}" hx-swap="outerHTML" hx-confirm="Finish exam?">Finish Exam</button>
{% else %}
<button hx-get="{% url 'physics:exam_complete' pk=exam.id cid=cid passcode=passcode %}" hx-swap="outerHTML" hx-confirm="Finish exam?">Finish Exam</button>
{% endif %}
{% endif %}
{% include "physics/exam_take_help.html" %}
{% endblock %}
{% block js %}
<script>
$(document).ready(() => {
{% if not exam.publish_results %}
{% if not exam.publish_results and not cid_user_exam.completed %}
let time_limit = '{{exam.time_limit}}'
if (time_limit != "None") {
+10
View File
@@ -33,6 +33,16 @@ urlpatterns.extend(
views.exam_take_overview,
name="exam_take_overview_user",
),
path(
"exam/<int:pk>/<str:cid>/<str:passcode>/complete",
views.exam_complete,
name="exam_complete",
),
path(
"exam/<int:pk>/complete",
views.exam_complete,
name="exam_complete_user",
),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path(
+12 -1
View File
@@ -136,6 +136,17 @@ def exam_start(request, pk):
},
)
def exam_complete(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
exam.check_user_can_take(cid, passcode, request.user)
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
cid_user_exam.complete_exam()
return HttpResponse("<div role='alert' class='alert alert-info'>Exam completed</div>")
def exam_take_overview(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
@@ -213,7 +224,7 @@ def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| N
form = UserAnswerForm(request.POST, instance=answer)
else:
form = UserAnswerForm(request.POST)
if form.is_valid() and not exam.publish_results:
if form.is_valid() and not exam.publish_results and not cid_user_exam.completed:
answer = form.save(commit=False)
if cid is not None:
+12 -7
View File
@@ -3,13 +3,6 @@ body {
background-color: black;
}
a, a:link {
color: #3282b8;
}
.navbar .nav-link a {
color: #3282b8;
}
.warn {
color: red;
@@ -1323,3 +1316,15 @@ details.help-text > summary {
padding-bottom: 1px;
}
a, a:link {
color: #3282b8;
}
.navbar .nav-link {
color: #3282b8;
}
span#user-id {
opacity: 50%;
float: right;
}
+5 -2
View File
@@ -9,13 +9,13 @@
{% endif %}
{% if request.user.is_authenticated and valid_user %}
<a href="{% url 'sbas:exam_take_user' pk=exam.pk sk=0 %}">Start Exam</a>
<a href="{% url 'sbas:exam_take_user' pk=exam.pk sk=0 %}" title="Click to start the exam"><button class="btn btn-primary">Start Exam</button></a>
{% else %}
Enter your CID and passcode in the below boxes.<br />
<p><input id="cid-box" type="text" value="Candidate ID"></p>
<p><input id="passcode-box" type="text" value="Passcode"></p>
<button>Start exam</button>
<button type="button" class="btn btn-primary">Start exam</button>
<script type="text/javascript">
$(document).ready(function () {
@@ -52,4 +52,7 @@
});
</script>
{% endif %}
{% include "sbas/exam_take_help.html" %}
{% endblock %}
+9 -17
View File
@@ -15,6 +15,10 @@
<div class="alert alert-primary review-mode-alert" role="alert">
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')">here</a>.
</div>
{% elif cid_user_exam.completed %}
<div class="alert alert-primary review-mode-alert" role="alert">
Exam completed.
</div>
{% endif %}
<div>
@@ -62,26 +66,14 @@
<div id="menu-list">
</div>
<details><summary>Help</summary>
<p>Each question contains a list of 5 different statements. One of these is the single BEST answer.</p>
<p>Click on the correct statement to select it. Once selected it will be highlighted. </p>
<p>Your answers are saved when navigating between questions (or if the save button is clicked on the final question). An overview of all the questions can be seen by clicking on the overview button (or by clicking <a target="_blank" href="
{% if cid %}
{% url 'sbas:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode %}
{% else %}
{% url 'sbas:exam_scores_user' pk=exam.pk %}
{% endif %}
"
>here</a>).</p>
</details>
{% include "sbas/exam_take_help.html" %}
{% endblock %}
{% block js %}
<script>
$(document).ready(() => {
/* beautify ignore:start */
{% if not exam.publish_results %}
{% if not exam.publish_results and not cid_user_exam.completed %}
let time_limit = '{{exam.time_limit}}'
if (time_limit != "None") {
@@ -97,7 +89,7 @@
$(el).addClass("selected")
})
})
{% else %}
{% elif exam.publish_results %}
$("ul.sba-answer-list li[data-ans='{{question.best_answer}}']").addClass("correct");
{% endif %}
@@ -130,12 +122,12 @@
.selected {
border: 1px solid purple;
}
li:hover::before {
.sba-answer-list li:hover::before {
content: "Click to select";
float: right
}
li:hover.selected::before {
.sba-answer-list li:hover.selected::before {
content: "Chosen answer";
float: right
}
+15
View File
@@ -0,0 +1,15 @@
<details class="help-text pt-3">
<summary><i class="bi bi-info-circle"></i> Help</summary>
<p>Each question contains a list of 5 different statements. One of these is the single BEST answer.</p>
<p>Click on the correct statement to select it. Once selected it will be highlighted. </p>
<p>Your answers are saved when navigating between questions (or if the save button is clicked on the final question). An overview of all the questions can be seen by clicking on the overview button.</p>
<p>Results are also available on the results page
<a target="_blank" href="
{% if cid %}
{% url 'sbas:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode %}
{% else %}
{% url 'sbas:exam_scores_user' pk=exam.pk %}
{% endif %}
">here</a>.</p>
</details>
+25 -6
View File
@@ -2,7 +2,7 @@
{% block content %}
<span id="user-id">
{% if request.user.is_authenticated %}
{% if not cid %}
User: {{request.user}}
{% else %}
CID: {{cid}}
@@ -15,16 +15,22 @@
{% if exam.publish_results %}
<div class="alert alert-primary review-mode-alert" role="alert">
Exam is in review mode. Score are available
{% if request.user.is_authenticated %}
{% if not cid %}
<a href="{% url 'sbas:exam_scores_user' pk=exam.id %}">here</a>
{% else %}
<a href="{% url 'sbas:exam_scores_cid_user' pk=exam.id cid=cid passcode=passcode %}">here</a>
{% endif %}
</div>
{% elif cid_user_exam.completed %}
<div class="alert alert-primary review-mode-alert" role="alert">
Exam completed. Answers are available
{% if not cid %}
<a href="{% url 'sbas:exam_scores_user' pk=exam.id %}">here</a>
{% else %}
<a href="{% url 'sbas:exam_scores_cid_user' pk=exam.id cid=cid passcode=passcode %}">here</a>
{% endif %}
{% if not exam.publish_results and answer_count != exam_length %}
</div>
{% elif answer_count != exam_length %}
<div id="unanswered-questions-alert" class="alert alert-warning" role="alert">
You have unanswered questions.
</div>
@@ -44,9 +50,22 @@
</div>
<div id="time-details">
Start time: {{cid_user_exam.start_time}}<br/>
Last change time: {{cid_user_exam.end_time}}
Last change time: {{cid_user_exam.end_time}}<br/>
Completed: {{cid_user_exam.completed}}
</div>
{% if not cid_user_exam.completed %}
{% if not cid %}
<button hx-get="{% url 'sbas:exam_complete_user' pk=exam.id %}" hx-swap="outerHTML" hx-confirm="Finish exam?">Finish Exam</button>
{% else %}
<button hx-get="{% url 'sbas:exam_complete' pk=exam.id cid=cid passcode=passcode %}" hx-swap="outerHTML" hx-confirm="Finish exam?">Finish Exam</button>
{% endif %}
{% endif %}
{% include "sbas/exam_take_help.html" %}
{% endblock %}
{% block js %}
+10
View File
@@ -45,6 +45,16 @@ urlpatterns = [
views.exam_take_overview,
name="exam_take_overview_user",
),
path(
"exam/<int:pk>/<str:cid>/<str:passcode>/complete",
views.exam_complete,
name="exam_complete",
),
path(
"exam/<int:pk>/complete",
views.exam_complete,
name="exam_complete_user",
),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
+12 -1
View File
@@ -116,6 +116,17 @@ def exam_start(request, pk):
{"exam": exam, "valid_user": exam.check_logged_in_user(request)},
)
def exam_complete(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
exam.check_user_can_take(cid, passcode, request.user)
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
cid_user_exam.complete_exam()
return HttpResponse("<div role='alert' class='alert alert-info'>Exam completed</div>")
def exam_take_overview(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
@@ -195,7 +206,7 @@ def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None):
form = UserAnswerForm(request.POST, instance=answer)
else:
form = UserAnswerForm(request.POST)
if form.is_valid():
if form.is_valid() and not exam.publish_results and not cid_user_exam.completed:
answer = form.save(commit=False)
if cid is not None:
+6 -1
View File
@@ -10,7 +10,7 @@
<details class="help-text">
<summary><i class="bi bi-info-circle"></i> Help</summary>
{% if collection %}
<p>This page shows the examinations within the collection "{{collection.name}}".</p>
<p>This page shows all the examinations within the collection "{{collection.name}}".</p>
{% else %}
<p>This page shows the currently active examinations (that you have access to manage).</p>
{% endif %}
@@ -87,11 +87,16 @@
</ul>
{% if collection %}
{% else %}
{% if view_all %}
<a href="{% url app_name|add:':exam_list' %}" >Hide archived</a>
{% else %}
<a href="{% url app_name|add:':exam_list_all' %}" title="Show all exams (including archived)">View all</a>
{% endif %}
{% endif %}
</div>
{% include 'exam_overview_js.html' %}
+4 -6
View File
@@ -1,8 +1,6 @@
{% extends 'base.html' %}
{% load auth_extras %}
{% block content %}
<p>
<div class="anatomy">
<h1>PENRA Courses</h1>
{% if not request.user.is_authenticated %}
<a href="{% url 'login'%}">Log in</a><br/>
@@ -15,18 +13,18 @@
<a href="{% url 'longs:index'%}">Longs</a>
<a href="{% url 'sbas:index'%}">SBAs</a> {% endcomment %}
{% endif %}
</div>
</p>
{% if request.user.is_authenticated %}
{% if request.user.userprofile.peninsula_trainee %}
<div id="user-details">
Please check your details are correct.<br/>
<div id="user-details card">
<div class="card-body">
<h5 class="card-title">Please check your details are correct.</h5>
Name: {{request.user.first_name}} {{request.user.last_name}}<br/>
Email: {{request.user.email}}<br/>
Grade: {{request.user.userprofile.grade}}<br/>
Supervisor: {{request.user.userprofile.supervisor}}
</div>
</div>