Compare commits
13
Commits
9bf68cd136
...
dc4cf0c3a2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc4cf0c3a2 | ||
|
|
99b951b32b | ||
|
|
c2c8ec5b13 | ||
|
|
6d5303cd0d | ||
|
|
f6b990a00a | ||
|
|
3c0deec140 | ||
|
|
e3241b503d | ||
|
|
04174484b1 | ||
|
|
168a4126d3 | ||
|
|
a0850c0030 | ||
|
|
a882a3592b | ||
|
|
8074b56a80 | ||
|
|
2e79df1eae |
+4
-2
@@ -783,10 +783,12 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
if self.start_date >= timezone.now() or (
|
if self.start_date >= timezone.now() or (
|
||||||
self.end_date is not None and self.end_date <= timezone.now()
|
self.end_date is not None and self.end_date <= timezone.now()
|
||||||
):
|
):
|
||||||
raise Http404("Exam not found")
|
# Raise with an explanatory message so the frontend can
|
||||||
|
# surface the reason to the end user.
|
||||||
|
raise Http404("Exam not currently available (outside allowed dates)")
|
||||||
# If it is not active no one can take
|
# If it is not active no one can take
|
||||||
elif active_only and not self.active:
|
elif active_only and not self.active:
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam currently inactive")
|
||||||
|
|
||||||
# If candidates only check they have access
|
# If candidates only check they have access
|
||||||
if self.candidates_only:
|
if self.candidates_only:
|
||||||
|
|||||||
+1
-1
@@ -455,7 +455,7 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
|
|||||||
name="exam_user_bulk_edit",
|
name="exam_user_bulk_edit",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores",
|
"exam/<int:pk>/scores/all",
|
||||||
generic_exam_view.exam_scores_all,
|
generic_exam_view.exam_scores_all,
|
||||||
name="exam_scores_all",
|
name="exam_scores_all",
|
||||||
),
|
),
|
||||||
|
|||||||
+2
-1
@@ -3576,7 +3576,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
|
|
||||||
# Load questions; prefetch 'answers' for apps that use them to avoid repeated DB hits
|
# Load questions; prefetch 'answers' for apps that use them to avoid repeated DB hits
|
||||||
questions_qs = exam.get_questions()
|
questions_qs = exam.get_questions()
|
||||||
if self.app_name not in ("physics",):
|
if self.app_name not in ("physics", "sbas"):
|
||||||
questions_qs = questions_qs.prefetch_related("answers")
|
questions_qs = questions_qs.prefetch_related("answers")
|
||||||
|
|
||||||
# Materialize questions list for iteration
|
# Materialize questions list for iteration
|
||||||
@@ -3694,6 +3694,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
#"cid_user_exam": cid_user_exam,
|
#"cid_user_exam": cid_user_exam,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template_context["normalised_score"] = None
|
||||||
if self.normalise_score is not None:
|
if self.normalise_score is not None:
|
||||||
normalised_score = self.normalise_score(total_score)
|
normalised_score = self.normalise_score(total_score)
|
||||||
template_context["normalised_score"] = normalised_score
|
template_context["normalised_score"] = normalised_score
|
||||||
|
|||||||
+4
-1
@@ -686,7 +686,10 @@ def stats(request):
|
|||||||
|
|
||||||
# HTTP Error 400
|
# HTTP Error 400
|
||||||
def page_not_found(request, exception):
|
def page_not_found(request, exception):
|
||||||
response = render(request, "404.html")
|
# Provide the exception message (reason) to the template so callers
|
||||||
|
# can show a user-friendly explanation when available.
|
||||||
|
context = {"reason": str(exception) if exception is not None else "Not found"}
|
||||||
|
response = render(request, "404.html", context=context)
|
||||||
|
|
||||||
response.status_code = 404
|
response.status_code = 404
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h2>Start exam: {{exam}}</h2>
|
<h2>Start exam: {{ exam }}</h2>
|
||||||
|
|
||||||
{% if exam.time_limit %}
|
{% 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.<br/>
|
This exam has a time limit of {{ exam.get_time_limit }}. The time will start when you click the Start Exam button below.<br/>
|
||||||
@@ -44,7 +44,10 @@
|
|||||||
let passcode = $("#passcode-box").val();
|
let passcode = $("#passcode-box").val();
|
||||||
|
|
||||||
if (Number.isInteger(parseInt(cid))) {
|
if (Number.isInteger(parseInt(cid))) {
|
||||||
window.location.replace("{% url 'sbas:exam_take' pk=exam.pk sk=0 cid='0000000' passcode='ZZZZZZ' %}".replace("0000000", cid).replace("ZZZZZZ", passcode));
|
// reverse a URL with safe placeholders and replace them client-side
|
||||||
|
const template = "{% url 'sbas:exam_take' pk=exam.pk sk=0 cid=0 passcode='0' %}"; // e.g. /sbas/exam/1/0/0/0/take
|
||||||
|
const url = template.replace('/0/0/take', `/${cid}/${passcode}/take`);
|
||||||
|
window.location.replace(url);
|
||||||
} else {
|
} else {
|
||||||
alert("Please enter a valid Candidate ID (CID).")
|
alert("Please enter a valid Candidate ID (CID).")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
|
|
||||||
<details class="help-text pt-3">
|
<details class="help-text pt-3">
|
||||||
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
<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>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>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>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
|
{% if cid %}
|
||||||
<a target="_blank" href="
|
{% url 'sbas:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode as scores_url %}
|
||||||
{% if cid %}
|
{% else %}
|
||||||
{% url 'sbas:exam_scores_cid_user' pk=exam.pk cid=cid passcode=passcode %}
|
{% url 'sbas:exam_scores_user' pk=exam.pk as scores_url %}
|
||||||
{% else %}
|
{% endif %}
|
||||||
{% url 'sbas:exam_scores_user' pk=exam.pk %}
|
<p>Results are also available on the results page <a target="_blank" href="{{ scores_url }}">here</a>.</p>
|
||||||
{% endif %}
|
</details>
|
||||||
">here</a>.</p>
|
|
||||||
</details>
|
|
||||||
@@ -134,16 +134,6 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("question/<int:pk>/toggle_frcr/", views.toggle_frcr_appropriate, name="toggle_frcr"),
|
path("question/<int:pk>/toggle_frcr/", views.toggle_frcr_appropriate, name="toggle_frcr"),
|
||||||
path("category/merge/", views.merge_category, name="merge_category"),
|
path("category/merge/", views.merge_category, name="merge_category"),
|
||||||
#path(
|
|
||||||
# "exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
|
||||||
# views.exam_scores_cid_user,
|
|
||||||
# name="exam_scores_cid_user",
|
|
||||||
#),
|
|
||||||
#path(
|
|
||||||
# "exam/<int:pk>/scores/",
|
|
||||||
# views.exam_scores_cid_user,
|
|
||||||
# name="exam_scores_user",
|
|
||||||
#),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns.extend(generic_view_urls(views.GenericViews))
|
urlpatterns.extend(generic_view_urls(views.GenericViews))
|
||||||
|
|||||||
@@ -370,6 +370,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
|
|||||||
|
|
||||||
|
|
||||||
def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None):
|
def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None):
|
||||||
|
logger.debug(f"Taking exam pk={pk} sk={sk} cid={cid} passcode={passcode} user={request.user}")
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.active:
|
if not exam.active:
|
||||||
|
|||||||
+9
-5
@@ -1,10 +1,14 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="">
|
<div class="">
|
||||||
<h2>404 error</h2>
|
<h2>404 error</h2>
|
||||||
{{ reason }}
|
{% if reason %}
|
||||||
|
{{ reason }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h3>{{resolved}}</h3>
|
{% if resolved %}
|
||||||
</div>
|
<h3>{{ resolved }}</h3>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<h2>Exam: {{ exam }}</h2>
|
<h2>Exam: {{ exam }}</h2>
|
||||||
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
|
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
|
||||||
|
|
||||||
{% if cid_user_exam %}
|
{% comment %} {% if cid_user_exam %}
|
||||||
{% if exam.results_supervisor_visible %}
|
{% if exam.results_supervisor_visible %}
|
||||||
Exam results are visible to supervisor.<br/>
|
Exam results are visible to supervisor.<br/>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -42,5 +42,5 @@
|
|||||||
>Toggle</button>
|
>Toggle</button>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
Answers:
|
Answers:
|
||||||
Reference in New Issue
Block a user