Added results CRUD and result detailed view

This commit is contained in:
Vivek Santayana 2021-12-04 12:20:03 +00:00
parent e69f60d813
commit 237aabf4ba
18 changed files with 576 additions and 59 deletions

View File

@ -64,6 +64,10 @@ class Test:
return test_code.replace('', '') return test_code.replace('', '')
def delete(self): def delete(self):
test = db.tests.find_one({'_id': self._id})
if 'entries' in test:
if test['entries']:
return jsonify({'error': 'Cannot delete an exam that has entries submitted to it.'}), 400
if self.dataset is None: if self.dataset is None:
self.dataset = db.tests.find_one({'_id': self._id})['dataset'] self.dataset = db.tests.find_one({'_id': self._id})['dataset']
if db.tests.delete_one({'_id': self._id}): if db.tests.delete_one({'_id': self._id}):

View File

@ -214,6 +214,11 @@ table.dataTable {
font-size: 14pt; font-size: 14pt;
} }
.result-action-buttons {
margin: 5px auto;
width: fit-content;
}
/* Fallback for Edge /* Fallback for Edge
-------------------------------------------------- */ -------------------------------------------------- */
@supports (-ms-ime-align: auto) { @supports (-ms-ime-align: auto) {

View File

@ -154,3 +154,42 @@ $('#dismiss-cookie-alert').click(function(event){
event.preventDefault(); event.preventDefault();
}) })
// Script for Resutlt Actions
$('.result-action-buttons').click(function(event){
var _id = $(this).data('_id')
if ($(this).data('result-action') == 'generate') {
$.ajax({
url: '/admin/certificate/',
type: 'POST',
data: JSON.stringify({'_id': _id}),
contentType: 'application/json',
dataType: 'html',
success: function(response) {
var display_window = window.open();
display_window.document.write(response);
},
error: function(response){
error_response(response);
},
});
} else {
var action = $(this).data('result-action')
$.ajax({
url: window.location.href,
type: 'POST',
data: JSON.stringify({'_id': _id, 'action': action}),
contentType: 'application/json',
success: function(response) {
if (action == 'delete') {
window.location.href = '/admin/results/';
} else window.location.reload();
},
error: function(response){
error_response(response);
},
});
}
});

View File

@ -32,8 +32,10 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<footer class="container site-footer"> <footer class="container site-footer mt-5">
{% block footer %}
{% include "admin/components/footer.html" %} {% include "admin/components/footer.html" %}
{% endblock %}
</footer> </footer>
<!-- JQuery, Popper, and Bootstrap js dependencies --> <!-- JQuery, Popper, and Bootstrap js dependencies -->
@ -70,5 +72,7 @@
></script> ></script>
{% block datatable_scripts %} {% block datatable_scripts %}
{% endblock %} {% endblock %}
{% block custom_data_script %}
{% endblock %}
</body> </body>
</html> </html>

View File

@ -0,0 +1,84 @@
{% extends "admin/components/base.html" %}
{% block title %} SKA Referee Test | Detailed Results {% endblock %}
{% block navbar %}{% endblock %}
{% block top_alerts %}{% endblock %}
{% block content %}
<div class="d-flex justify-content-center">
<h1 class="center">SKA Referee Theory Exam Results</h1>
</div>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-lg-6">
<ul class="list-group">
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Candidate</h5>
</div>
<h2>
{{ entry.name.surname}}, {{ entry.name.first_name }}
</h2>
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Email Address</h5>
</div>
{{ entry.email }}
</li>
{% if entry['club'] %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Club</h5>
</div>
{{ entry.club }}
</li>
{% endif %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Exam Code</h5>
</div>
{{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
</li>
{% if entry['user_code'] %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">User Code</h5>
</div>
{{ entry.user_code }}
</li>
{% endif %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Start Time</h5>
</div>
{{ entry.start_time.strftime('%d %b %Y %H:%M:%S') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Submission Time</h5>
{% if entry.status == 'late' %}
<span class="badge bg-danger">Late</span>
{% endif %}
</div>
{{ entry.submission_time.strftime('%d %b %Y %H:%M:%S') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Score</h5>
</div>
{{ entry.results.score }}&percnt;
</li>
<li class="list-group-item list-group-item-action {% if entry.results.grade == 'fail' %}list-group-item-danger {% elif entry.results.grade == 'merit' %} list-group-item-success {% endif %}">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Grade</h5>
</div>
{{ entry.results.grade[0]|upper }}{{ entry.results.grade[1:]}}
</li>
</ul>
<div class="site-footer mt-5">
These results were generated using the SKA RefTest web app on {{ now.strftime('%d %b %Y at %H:%M:%S') }}.
</div>
{% block footer %}{% endblock %}
</div>
</div>
</div>
{% endblock %}

View File

@ -6,6 +6,7 @@
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
{% endblock %} {% endblock %}
{% block datatable_scripts %} {% block datatable_scripts %}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
@ -23,5 +24,5 @@
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
{% block custom_data_script %}{% endblock %} <script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,175 @@
{% extends "admin/components/base.html" %}
{% block title %} SKA Referee Test | Detailed Results {% endblock %}
{% block content %}
{% include "admin/components/client-alerts.html" %}
<h1>Exam Results</h1>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6">
<ul class="list-group">
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Candidate</h5>
</div>
<h2>
{{ entry.name.surname}}, {{ entry.name.first_name }}
</h2>
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Email Address</h5>
</div>
{{ entry.email }}
</li>
{% if entry['club'] %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Club</h5>
</div>
{{ entry.club }}
</li>
{% endif %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Exam Code</h5>
</div>
{{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
</li>
{% if entry['user_code'] %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">User Code</h5>
</div>
{{ entry.user_code }}
</li>
{% endif %}
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Start Time</h5>
</div>
{{ entry.start_time.strftime('%d %b %Y %H:%M:%S') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Submission Time</h5>
{% if entry.status == 'late' %}
<span class="badge bg-danger">Late</span>
{% endif %}
</div>
{{ entry.submission_time.strftime('%d %b %Y %H:%M:%S') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Score</h5>
</div>
{{ entry.results.score }}&percnt;
</li>
<li class="list-group-item list-group-item-action {% if entry.results.grade == 'fail' %}list-group-item-danger {% elif entry.results.grade == 'merit' %} list-group-item-success {% endif %}">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Grade</h5>
</div>
{{ entry.results.grade[0]|upper }}{{ entry.results.grade[1:]}}
</li>
</ul>
<div class="accordion" id="results-breakdown">
<div class="accordion-item">
<h2 class="accordion-header" id="by-category">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#by-category-breakdown" aria-expanded="false" aria-controls="by-category-breakdown">
Score By Categories
</button>
</h2>
<div id="by-category-breakdown" class="accordion-collapse collapse" aria-labelledby="by-category" data-bs-parent="#results-breakdown">
<div class="accordion-body">
<table class="table table-striped">
<thead>
<tr>
<th>
Category
</th>
<th>
Score
</th>
<th>
Max
</th>
</tr>
</thead>
<tbody>
{% for tag, scores in entry.results.tags.items() %}
<tr>
<td>
{{ tag }}
</td>
<td>
{{ scores.scored }}
</td>
<td>
{{scores.max}}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="by-question">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#by-question-breakdown" aria-expanded="false" aria-controls="by-question-breakdown">
View All Answers
</button>
</h2>
<div id="by-question-breakdown" class="accordion-collapse collapse" aria-labelledby="by-question" data-bs-parent="#results-breakdown">
<div class="accordion-body">
<table class="table table-striped">
<thead>
<tr>
<th>
Question
</th>
<th>
Answer
</th>
</tr>
</thead>
<tbody>
{% for question, answer in entry.answers.items() %}
<tr>
<td>
{{ question }}
</td>
<td>
{{ answer }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="container justify-content-center">
<div class="row">
<a href="#" class="btn btn-primary result-action-buttons" data-result-action="generate" data-_id="{{ entry._id }}">
<i class="bi bi-printer-fill button-icon"></i>
Printable Version
</a>
</div>
<div class="row">
{% if entry.status == 'late' %}
<a href="#" class="btn btn-warning result-action-buttons" data-result-action="override" data-_id="{{ entry._id }}">
<i class="bi bi-clock-history button-icon"></i>
Allow Late Entry
</a>
{% endif %}
<a href="#" class="btn btn-danger result-action-buttons" data-result-action="delete" data-_id="{{ entry._id }}">
<i class="bi bi-trash-fill button-icon"></i>
Delete Result
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1 +1,138 @@
{% extends "admin/components/base.html" %} {% extends "admin/components/datatable.html" %}
{% block title %} SKA Referee Test | View Results {% endblock %}
{% block content %}
{% include "admin/components/client-alerts.html" %}
<h1>View Results</h1>
{% if entries %}
<table id="results-table" class="table table-striped" style="width:100%">
<thead>
<tr>
<th data-priority="1">
Name
</th>
<th data-priority="4">
Club
</th>
<th data-priority="5">
Exam Code
</th>
<th data-priority="3">
Status
</th>
<th data-priority="4">
Submitted
</th>
<th data-priority="2">
Result
</th>
<th data-priority="3">
Grade
</th>
<th data-priority="1">
Details
</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr class="table-row">
<td>
{{ entry.name.surname }}, {{ entry.name.first_name }}
</td>
<td>
{% if 'club' in entry %}
{{ entry.club }}
{% endif %}
</td>
<td>
{{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
</td>
<td>
{% if 'status' in entry %}
{{ entry.status }}
{% endif %}
</td>
<td>
{% if 'submission_time' in entry %}
{{ entry.submission_time.strftime('%d %b %Y') }}
{% endif %}
</td>
<td>
{% if 'results' in entry %}
{{ entry.results.score }}&percnt;
{% endif %}
</td>
<td>
{% if 'results' in entry %}
{{ entry.results.grade }}
{% endif %}
</td>
<td class="row-actions">
<a
href="{{ url_for('admin_views.view_entry', _id = entry._id ) }}"
class="btn btn-primary entry-details"
data-_id="{{entry._id}}"
title="View Details"
>
<i class="bi bi-file-medical-fill button-icon"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-primary alert-db-empty">
<i class="bi bi-info-circle-fill" aria-title="Alert" title="Alert"></i>
There are no exam attempts to view.
</div>
{% endif %}
{% endblock %}
{% if entries %}
{% block custom_data_script %}
<script>
$(document).ready(function() {
$('#results-table').DataTable({
'searching': false,
'columnDefs': [
{'sortable': false, 'targets': [7]},
{'searchable': false, 'targets': [7]}
],
'order': [[4, 'desc'], [0, 'asc']],
'buttons': [
{
extend: 'print',
exportOptions: {
columns: [0, 1, 3, 4, 5, 6]
}
},
{
extend: 'excel',
exportOptions: {
columns: [0, 1, 3, 4, 5, 6]
}
},
{
extend: 'pdf',
exportOptions: {
columns: [0, 1, 3, 4, 5, 6]
}
}
],
'responsive': 'true',
'colReorder': 'true',
'fixedHeader': 'true',
'searchBuilder': {
depthLimit: 2,
columns: [1, 5, 6],
},
dom: 'BQlfrtip'
});
// $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') -->
} );
$('#results-table').show();
$(window).trigger('resize');
</script>
{% endblock %}
{% endif %}

View File

@ -20,7 +20,7 @@
Author Author
</th> </th>
<th data-priority="3"> <th data-priority="3">
Use Exams
</th> </th>
<th data-priority="1"> <th data-priority="1">
Actions Actions

View File

@ -1,6 +1,7 @@
{% extends "admin/components/datatable.html" %} {% extends "admin/components/datatable.html" %}
{% block title %} SKA Referee Test | Manage Exams {% endblock %} {% block title %} SKA Referee Test | Manage Exams {% endblock %}
{% block content %} {% block content %}
{% include "admin/components/client-alerts.html" %}
<h1>Manage Exams</h1> <h1>Manage Exams</h1>
{% include "admin/components/secondary-navs/tests.html" %} {% include "admin/components/secondary-navs/tests.html" %}
<h2>{{ display_title }}</h2> <h2>{{ display_title }}</h2>
@ -157,7 +158,7 @@
}); });
// $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') --> // $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') -->
} ); } );
$('#test-table').show(); $('#active-test-table').show();
$(window).trigger('resize'); $(window).trigger('resize');
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
from flask import Blueprint, render_template, flash, redirect, request, jsonify, abort from flask import Blueprint, render_template, flash, redirect, request, jsonify, abort, make_response
from flask.helpers import url_for from flask.helpers import url_for
from functools import wraps from functools import wraps
from datetime import datetime from datetime import datetime
@ -14,7 +14,7 @@ from main import app, db
from uuid import uuid4 from uuid import uuid4
import secrets import secrets
from main import mail from main import mail
from datetime import datetime, date, timedelta from datetime import datetime, date
from .models.tests import Test from .models.tests import Test
from common.data_tools import get_default_dataset, get_time_options, available_datasets from common.data_tools import get_default_dataset, get_time_options, available_datasets
@ -396,14 +396,65 @@ def create_test():
return jsonify({ 'error': errors}), 400 return jsonify({ 'error': errors}), 400
@views.route('/tests/delete/', methods=['POST']) @views.route('/tests/delete/', methods=['POST'])
@admin_account_required
@login_required
def delete_test(): def delete_test():
_id = request.get_json()['_id'] _id = request.get_json()['_id']
if db.tests.find_one({'_id': _id}): if db.tests.find_one({'_id': _id}):
return Test(_id = _id).delete() return Test(_id = _id).delete()
return jsonify({'error': 'Could not find the corresponding test to delete.'}), 404 return jsonify({'error': 'Could not find the corresponding test to delete.'}), 404
@views.route('/test/<id>/', methods=['GET','POST']) @views.route('/test/<_id>/', methods=['GET','POST'])
@admin_account_required
@login_required
def view_test(_id, filter=''): def view_test(_id, filter=''):
test = db.tests.find_one({'_id':_id}) test = db.tests.find_one({'_id':_id})
if not test: if not test:
return abort(404) return abort(404)
@views.route('/results/')
@admin_account_required
@login_required
def view_entries():
entries = decrypt_find(db.entries, {})
return render_template('/admin/results.html', entries = entries)
@views.route('/results/<_id>/', methods = ['GET', 'POST'])
@admin_account_required
@login_required
def view_entry(_id=''):
entry = decrypt_find_one(db.entries, {'_id': _id})
if request.method == 'GET':
if not entry:
return abort(404)
return render_template('/admin/result-detail.html', entry = entry)
if request.method == 'POST':
if not entry:
return jsonify({'error': 'A valid entry could no be found.'}), 404
action = request.get_json()['action']
if action == 'override':
late_ignore = db.entries.find_one_and_update({'_id': _id}, {'$set': {'status': 'late (allowed)'}})
if late_ignore:
flash('Late status for the entry has been allowed.', 'success')
return jsonify({'success': 'Late status allowed.'}), 200
return jsonify({'error': 'An error occurred.'}), 400
if action == 'delete':
test_code = entry['test_code']
test = db.tests.find_one_and_update({'test_code': test_code}, {'$pull': {'entries': _id}})
if not test:
return jsonify({'error': 'A valid exam could not be found.'}), 404
delete = db.entries.delete_one({'_id': _id})
if delete:
flash('Entry has been deleted.', 'success')
return jsonify({'success': 'Entry has been deleted.'}), 200
return jsonify({'error': 'An error occurred.'}), 400
@views.route('/certificate/', methods=['POST'])
@admin_account_required
@login_required
def generate_certificate():
_id = request.get_json()['_id']
entry = decrypt_find_one(db.entries, {'_id': _id})
if not entry:
return abort(404)
return render_template('/admin/components/certificate.html', entry = entry)

View File

@ -172,6 +172,11 @@ body {
font-size: 26pt; font-size: 26pt;
} }
.button-icon {
font-size: 20px;
margin-right: 2px;
}
/* Change Autocomplete styles in Chrome*/ /* Change Autocomplete styles in Chrome*/
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,

View File

@ -363,7 +363,7 @@ function render_question() {
function check_answered() { function check_answered() {
var question = questions[current_question]; var question = questions[current_question];
var name = question.q_no; var name = question.q_no;
if (question_status[current_question] == 0) { if (question_status[current_question] == 0 || question_status[current_question] == -1) {
if (!$(`input[name='${name}']:checked`).val()) { if (!$(`input[name='${name}']:checked`).val()) {
question_status[current_question] = -1; question_status[current_question] = -1;
} else { } else {

View File

@ -13,6 +13,9 @@
The presentation of the questions is just a start, and we acknowledge we still have a long way to go. We welcome any feedback on how we can further improve this test. The presentation of the questions is just a start, and we acknowledge we still have a long way to go. We welcome any feedback on how we can further improve this test.
</p> </p>
<div class="button-container"> <div class="button-container">
<a href="{{ url_for('quiz_views.instructions') }}" class="btn btn-success">Read the Instructions</a> <a href="{{ url_for('quiz_views.instructions') }}" class="btn btn-success">
<i class="bi bi-book-fill button-icon"></i>
Read the Instructions
</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -50,6 +50,9 @@
</p> </p>
</div> </div>
<div class="button-container"> <div class="button-container">
<a href="{{ url_for('quiz_views.start') }}" class="btn btn-success">Take the Quiz</a> <a href="{{ url_for('quiz_views.start') }}" class="btn btn-success">
<i class="bi bi-pencil-fill button-icon"></i>
Take the Exam
</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -9,10 +9,10 @@
<span class="surname">{{ entry.name.surname }}</span>, {{ entry.name.first_name }} <span class="surname">{{ entry.name.surname }}</span>, {{ entry.name.first_name }}
</h3> </h3>
<strong class="results-details">Email Address</strong>: {{ entry.email }} <strong class="results-details">Email Address</strong>: {{ entry.email }} <br />
{% if entry.club %} {% if entry.club %}
<strong class="results-details">Club</strong>: {{ entry.club }} <strong class="results-details">Club</strong>: {{ entry.club }} <br />
{% endif%} {% endif%}
{% if entry.status == 'late' %} {% if entry.status == 'late' %}
@ -34,7 +34,7 @@
<ul> <ul>
{% for tag in tag_output %} {% for tag in tag_output %}
<li>{{ tag[0] }}</li> <li>{{ tag }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -34,7 +34,10 @@
<div class="container form-submission-button"> <div class="container form-submission-button">
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<button class="btn btn-md btn-success btn-block" type="submit">Start Quiz</button> <button class="btn btn-md btn-success btn-block" type="submit">
<i class="bi bi-pencil-fill button-icon"></i>
Start Exam
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -104,6 +104,7 @@ def fetch_questions():
'end_time': end_time 'end_time': end_time
} }
entry = db.entries.find_one_and_update({'_id': _id}, {'$set': update}, upsert=False, return_document=ReturnDocument.AFTER) entry = db.entries.find_one_and_update({'_id': _id}, {'$set': update}, upsert=False, return_document=ReturnDocument.AFTER)
db.tests.find_one_and_update({'_id': test['_id']}, {'$push': {'entries': _id}})
dataset = test['dataset'] dataset = test['dataset']
dataset_path = os.path.join(app.config['DATA_FILE_DIRECTORY'], dataset) dataset_path = os.path.join(app.config['DATA_FILE_DIRECTORY'], dataset)
with open(dataset_path, 'r') as data_file: with open(dataset_path, 'r') as data_file:
@ -172,6 +173,7 @@ def result():
elif entry['results']['grade'] == 'fail': elif entry['results']['grade'] == 'fail':
flavour_text_plain = """Unfortunately, you were not successful in passing the theory exam in this attempt. We hope that this does not dissuade you, and that you try again in the future. flavour_text_plain = """Unfortunately, you were not successful in passing the theory exam in this attempt. We hope that this does not dissuade you, and that you try again in the future.
""" """
if not entry['status'] == 'late':
email = Message( email = Message(
subject="SKA Refereeing Theory Exam Results", subject="SKA Refereeing Theory Exam Results",
recipients=[entry['email']], recipients=[entry['email']],
@ -200,7 +202,7 @@ def result():
<p><strong>Email Address</strong>: {entry['email']}</p> <p><strong>Email Address</strong>: {entry['email']}</p>
{f"<p><strong>Club</strong>: {entry['club']}</p>" if entry['club'] else ''} {f"<p><strong>Club</strong>: {entry['club']}</p>" if entry['club'] else ''}
<p><strong>Date of Test</strong>: {entry['submission_time'].strftime('%d %b %Y')}</p> <p><strong>Date of Test</strong>: {entry['submission_time'].strftime('%d %b %Y')}</p>
<h1>{score}%</h1> <h1>{score}&percnt;</h1>
<h2>{entry['results']['grade']}</h2> <h2>{entry['results']['grade']}</h2>
<p>{flavour_text_plain}</p> <p>{flavour_text_plain}</p>
<p>Based on your answers, we would also suggest you revise the following topics as you continue refereeing:</p> <p>Based on your answers, we would also suggest you revise the following topics as you continue refereeing:</p>