From 472fe6ee962fb55f40564ede809f01e18ea1d8b1 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 11 Dec 2023 13:57:02 +0000 Subject: [PATCH] improving physics / sba exams --- anatomy/static/js/anatomy.js | 46 ++++++ generic/models.py | 19 ++- physics/templates/physics/exam_start.html | 4 + physics/templates/physics/exam_take.html | 146 +++++------------- .../templates/physics/exam_take_overview.html | 28 +++- rad/static/css/anatomy.css | 55 ++++++- rad/views.py | 2 +- sbas/templates/sbas/exam_start.html | 4 + sbas/templates/sbas/exam_take.html | 129 +++------------- sbas/templates/sbas/exam_take_overview.html | 28 +++- templates/cid_selector.html | 6 +- templates/exam_clock.html | 18 +++ templates/index.html | 26 +++- 13 files changed, 280 insertions(+), 231 deletions(-) create mode 100644 templates/exam_clock.html diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js index 0493c7c2..057dea51 100644 --- a/anatomy/static/js/anatomy.js +++ b/anatomy/static/js/anatomy.js @@ -581,3 +581,49 @@ function delete_multiple(url, csrf_token) { } window.delete_multiple = delete_multiple + + +function getTimeRemaining(endtime) { + const total = Date.parse(endtime) - Date.parse(new Date()); + const seconds = Math.floor((total / 1000) % 60); + const minutes = Math.floor((total / 1000 / 60) % 60); + const hours = Math.floor((total / (1000 * 60 * 60)) % 24); + //const days = Math.floor(total / (1000 * 60 * 60 * 24)); + + return { + total, + //days, + hours, + minutes, + seconds + }; +} + +function initializeClock(id, endtime) { + const clock = document.getElementById(id); + clock.style.display = "block" + const daysSpan = clock.querySelector('.days'); + const hoursSpan = clock.querySelector('.hours'); + const minutesSpan = clock.querySelector('.minutes'); + const secondsSpan = clock.querySelector('.seconds'); + + function updateClock() { + const t = getTimeRemaining(endtime); + + //daysSpan.innerHTML = t.days; + hoursSpan.innerHTML = ('0' + t.hours).slice(-2); + minutesSpan.innerHTML = ('0' + t.minutes).slice(-2); + secondsSpan.innerHTML = ('0' + t.seconds).slice(-2); + + if (t.total <= 0) { + clearInterval(timeinterval); + timeup.style.display = "block" + clock.style.display = "none" + } + } + + const timeinterval = setInterval(updateClock, 1000); + updateClock(); +} + +window.initializeClock = initializeClock \ No newline at end of file diff --git a/generic/models.py b/generic/models.py index a21f73a7..dbf5ccec 100644 --- a/generic/models.py +++ b/generic/models.py @@ -1,4 +1,5 @@ from collections import defaultdict +import datetime import json import os from typing import Self, Tuple @@ -712,6 +713,20 @@ class ExamBase(ExamCollectionGenericBase): "{}:exam_json_cid".format(self.app_name), args=(self.pk, cid, passcode) ) + def get_time_limit(self): + """Returns a human readable time limit""" + h, m, s = str(datetime.timedelta(seconds=self.time_limit)).split(":") + + time_limit = "" + if s != "0": + time_limit = f" {s} second(s)" + if m != "0": + time_limit = f"{m} minute(s) {time_limit}" + if h != "0": + time_limit = f"{h} hour(s) {time_limit}" + + return time_limit.strip() + def get_question_index(self, question): return list(self.exam_questions.all()).index(question) @@ -955,10 +970,12 @@ class CidUser(models.Model): def check_passcode(self, passcode) -> bool: return self.passcode == passcode - def get_cid_exams(self): + def get_cid_exams(self, include_case_collections=True): available_exams = [] for n, t in EXAM_TYPES: + if not include_case_collections and n == "casecollection": + continue exam_rel = getattr(self, t) if exam_rel.exists(): exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name") diff --git a/physics/templates/physics/exam_start.html b/physics/templates/physics/exam_start.html index 96be8048..171e6e2c 100755 --- a/physics/templates/physics/exam_start.html +++ b/physics/templates/physics/exam_start.html @@ -11,6 +11,10 @@

+ {% if exam.time_limit %} + This exam has a time limit of {{ exam.get_time_limit }}. The time will start when you click the Start Exam button below.
+ {% endif %} + {% endblock %} diff --git a/rad/static/css/anatomy.css b/rad/static/css/anatomy.css index 9d4d8762..d5ec5891 100644 --- a/rad/static/css/anatomy.css +++ b/rad/static/css/anatomy.css @@ -73,7 +73,8 @@ a:link { font-size: 20; } -.hide, .hidden { +.hide, +.hidden { display: none; } @@ -83,7 +84,12 @@ a:link { } .unanswered { - color: gray; + color: darkred; + border-color: darkred; +} + +.unanswered:hover { + background-color: rgb(166, 0, 0); } #question-list { @@ -1057,7 +1063,50 @@ table .peninsula-trainee::before { user-select: none; } -.form-control, .form-control:focus, .form-control option { +.form-control, +.form-control:focus, +.form-control option { background-color: unset; color: unset } + +#clockdiv { + font-family: sans-serif; + display: inline-block; + font-weight: 100; + text-align: center; + font-size: 12px; + display: none; + color: darkgray; +} + +#clockdiv>div { + padding: 1px; + border-radius: 3px; + display: inline-block; +} + +#clockdiv div>span.num { + color: white; + padding: 1px; + border-radius: 3px; + display: inline-block; +} + +#timeup { + display: none; + color: red; + font-weight: 100; + text-align: center; + font-size: 12px; +} + +#time-details { + opacity: 50%; + font-size: small; +} + +.current-question { + background-color: #52057b; + color: grey; +} \ No newline at end of file diff --git a/rad/views.py b/rad/views.py index 83b6131d..5874e2bf 100644 --- a/rad/views.py +++ b/rad/views.py @@ -245,7 +245,7 @@ def cid_scores(request, cid, passcode): case_collections = cid_user.casecollection_exams.all() - available_exams = cid_user.get_cid_exams() + available_exams = cid_user.get_cid_exams(include_case_collections=False) return render( request, diff --git a/sbas/templates/sbas/exam_start.html b/sbas/templates/sbas/exam_start.html index 634140cc..3ffd3aaf 100755 --- a/sbas/templates/sbas/exam_start.html +++ b/sbas/templates/sbas/exam_start.html @@ -10,6 +10,10 @@ Enter your CID and passcode in the below boxes.

+ + {% if exam.time_limit %} + This exam has a time limit of {{ exam.get_time_limit }}. The time will start when you click the Start Exam button below.
+ {% endif %} diff --git a/sbas/templates/sbas/exam_take.html b/sbas/templates/sbas/exam_take.html index b4d40603..89236307 100755 --- a/sbas/templates/sbas/exam_take.html +++ b/sbas/templates/sbas/exam_take.html @@ -2,34 +2,13 @@ {% block content %} - {% if request.user.is_authenticated %} - User: {{request.user}} - {% else %} - CID: {{cid}} - {% endif %} + {% if request.user.is_authenticated %} + User: {{request.user}} + {% else %} + CID: {{cid}} + {% endif %} -
- Allocated time over. -
-
- Time remaining: - {% comment %}
- -
Days
-
{% endcomment %} -
- - Hours -
-
- - Minutes -
-
- - Seconds -
-
+ {% include "exam_clock.html" %}

{{exam.name}}: Question [{{pos|add:1}}/{{exam_length}}]

{% if exam.publish_results %} @@ -58,13 +37,13 @@
{% if previous > -1 %} - + {% endif %} {% if next %} - + {% else %} {% if not exam.publish_results %} - + {% endif %} {% endif %} @@ -73,7 +52,7 @@

{{question.feedback|safe}}

{% endif %}
- + @@ -83,6 +62,11 @@ +
Help +

Each question contains a list of 5 different statements. One of these is the single BEST answer.

+

Click on the correct statement to select it. Once selected it will be highlighted.

+

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 here).

+
{% endblock %} {% block js %} @@ -113,8 +97,10 @@ $("ul.sba-answer-list li[data-ans='{{saved_answer}}']").addClass("selected"); {% endif %} for (let i = 0; i < {{ exam_length }}; i++) { - $("#menu-list").append($( - ``)); + qbutton = $(``) + + if (i == {{pos}}) {qbutton.addClass("current-question")} + $("#menu-list").append(qbutton); } $("button.question-menu-item").on("click", (e) => { @@ -124,50 +110,6 @@ }); - function getTimeRemaining(endtime) { - const total = Date.parse(endtime) - Date.parse(new Date()); - const seconds = Math.floor((total / 1000) % 60); - const minutes = Math.floor((total / 1000 / 60) % 60); - const hours = Math.floor((total / (1000 * 60 * 60)) % 24); - //const days = Math.floor(total / (1000 * 60 * 60 * 24)); - - return { - total, - //days, - hours, - minutes, - seconds - }; - } - - function initializeClock(id, endtime) { - console.log("Start clock", endtime) - const clock = document.getElementById(id); - clock.style.display = "block" - const daysSpan = clock.querySelector('.days'); - const hoursSpan = clock.querySelector('.hours'); - const minutesSpan = clock.querySelector('.minutes'); - const secondsSpan = clock.querySelector('.seconds'); - - function updateClock() { - const t = getTimeRemaining(endtime); - - //daysSpan.innerHTML = t.days; - hoursSpan.innerHTML = ('0' + t.hours).slice(-2); - minutesSpan.innerHTML = ('0' + t.minutes).slice(-2); - secondsSpan.innerHTML = ('0' + t.seconds).slice(-2); - - if (t.total <= 0) { - clearInterval(timeinterval); - timeup.style.display = "block" - clock.style.display = "none" - } - } - - const timeinterval = setInterval(updateClock, 1000); - updateClock(); - } - /* beautify ignore:end */ }) {% endblock %} @@ -180,36 +122,15 @@ .selected { border: 1px solid purple; } - - #clockdiv{ - font-family: sans-serif; - display: inline-block; - font-weight: 100; - text-align: center; - font-size: 12px; - display: none; - color: darkgray; + li:hover::before { + content: "Click to select"; + float: right } - #clockdiv > div{ - padding: 1px; - border-radius: 3px; - {% comment %} background: purple; {% endcomment %} - display: inline-block; - } - #clockdiv div > span.num{ - color: white; - padding: 1px; - border-radius: 3px; - display: inline-block; + li:hover.selected::before { + content: "Chosen answer"; + float: right } - #timeup { - display: none; - color: red; - font-weight: 100; - text-align: center; - font-size: 12px; - } {% endblock %} \ No newline at end of file diff --git a/sbas/templates/sbas/exam_take_overview.html b/sbas/templates/sbas/exam_take_overview.html index 4836aaa7..227e8d87 100644 --- a/sbas/templates/sbas/exam_take_overview.html +++ b/sbas/templates/sbas/exam_take_overview.html @@ -1,12 +1,15 @@ {% extends 'base.html' %} {% block content %} + {% if request.user.is_authenticated %} User: {{request.user}} {% else %} CID: {{cid}} {% endif %} + + {% include "exam_clock.html" %}

Exam: {{exam.name}}

{% if exam.publish_results %} @@ -21,26 +24,43 @@ {% endif %} -

Questions

-
{{answer_count}} out of {{exam_length}} questions answered. Click to go to question.
+ {% if not exam.publish_results and answer_count != exam_length %} + + {% endif %} + + +
{{answer_count}} out of {{exam_length}} questions answered. Unanswered questions are shown in red.

Click to go to a question.

{% for question, answer in question_answer_tuples %} {% if request.user.is_authenticated %} - + {% else %} - + {% endif %} {% endfor %}
+
Start time: {{cid_user_exam.start_time}}
Last change time: {{cid_user_exam.end_time}} +
{% endblock %} {% block js %} {% endblock %} diff --git a/templates/cid_selector.html b/templates/cid_selector.html index 441f7e53..1194c7e3 100644 --- a/templates/cid_selector.html +++ b/templates/cid_selector.html @@ -4,8 +4,10 @@

Exam / Results checker

Please enter your CID (Candidate ID) and passcode

-

CID:

-

Passcode:

+ + + +
CID:
Passcode:
diff --git a/templates/exam_clock.html b/templates/exam_clock.html new file mode 100644 index 00000000..673030be --- /dev/null +++ b/templates/exam_clock.html @@ -0,0 +1,18 @@ +
+ Allocated time over. +
+
+ Time remaining: +
+ + Hours +
+
+ + Minutes +
+
+ + Seconds +
+
\ No newline at end of file diff --git a/templates/index.html b/templates/index.html index f9866a42..998f564a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -15,19 +15,38 @@ Longs SBAs {% endcomment %} {% endif %} - {% if request.user.is_staff %}Admin{% endif %}

{% if request.user.is_authenticated %} + + + {% if request.user.userprofile.peninsula_trainee %} +
+ Please check your details are correct.
+ Name: {{request.user.first_name}} {{request.user.last_name}}
+ Email: {{request.user.email}}
+ Grade: {{request.user.userprofile.grade}}
+ Supervisor: {{request.user.userprofile.supervisor}} + +
+ + {% endif %} + +

- Your Exams +

{% endif %}

RTS is available here

-

CID users can log in here

+ + + {% if not request.user.is_authenticated %} +

CID users can log in here

+ {% endif %} +
{% if request.user.is_staff %} Manage users here and candidates here @@ -35,6 +54,7 @@

Manage Examinations

+ Admin {% endif %}
{% endblock %}