Finished admin console

This commit is contained in:
2022-06-15 23:54:44 +01:00
parent 62160beab2
commit 2ea778143e
33 changed files with 198 additions and 206 deletions

@ -70,14 +70,14 @@ $('form[name=form-upload-questions]').submit(function(event) {
// Edit and Delete Test Button Handlers
$('.test-action').click(function(event) {
let _id = $(this).data('_id');
let id = $(this).data('id');
let action = $(this).data('action');
if (action == 'delete') {
if (action == 'delete' || action == 'start' || action == 'end') {
$.ajax({
url: `/admin/tests/delete/`,
url: `/admin/tests/edit/`,
type: 'POST',
data: JSON.stringify({'_id': _id}),
data: JSON.stringify({'id': id, 'action': action}), // TODO Change how CRUD operations work
contentType: 'application/json',
success: function(response) {
window.location.href = '/admin/tests/';
@ -87,21 +87,7 @@ $('.test-action').click(function(event) {
},
});
} else if (action == 'edit') {
window.location.href = `/admin/test/${_id}/`
} else if (action == 'close'){
$.ajax({
url: `/admin/tests/close/`,
type: 'POST',
data: JSON.stringify({'_id': _id}),
contentType: 'application/json',
success: function(response) {
$(window).scrollTop(0);
window.location.reload();
},
error: function(response){
error_response(response);
},
});
window.location.href = `/admin/test/${id}/`
}
event.preventDefault();
@ -185,13 +171,13 @@ $('#dismiss-cookie-alert').click(function(event){
// Script for Result Actions
$('.result-action-buttons').click(function(event){
var _id = $(this).data('_id');
var id = $(this).data('id');
if ($(this).data('result-action') == 'generate') {
$.ajax({
url: '/admin/certificate/',
type: 'POST',
data: JSON.stringify({'_id': _id}),
data: JSON.stringify({'id': id}),
contentType: 'application/json',
dataType: 'html',
success: function(response) {
@ -207,7 +193,7 @@ $('.result-action-buttons').click(function(event){
$.ajax({
url: window.location.href,
type: 'POST',
data: JSON.stringify({'_id': _id, 'action': action}),
data: JSON.stringify({'id': id, 'action': action}),
contentType: 'application/json',
success: function(response) {
if (action == 'delete') {

@ -2,7 +2,7 @@
{% block content %}
<div class="form-container">
<form name="form-update-account" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_views.home') }}">
<form name="form-update-account" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._home') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Update Your Account</h2>
{{ form.hidden_tag() }}
@ -32,7 +32,7 @@
<div class="container form-submission-button">
<div class="row">
<div class="col text-center">
<a href="{{ url_for('admin_views.users') }}" class="btn btn-md btn-danger btn-block" type="button">
<a href="{{ url_for('admin._users') }}" class="btn btn-md btn-danger btn-block" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
</svg>

@ -2,7 +2,7 @@
{% block content %}
<div class="form-container">
<form name="form-login" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_views.home') }}">
<form name="form-login" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._home') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form">Log In</h2>
{{ form.hidden_tag() }}
@ -26,7 +26,7 @@
</div>
</div>
</div>
<a href="{{ url_for('admin_auth.reset') }}" class="signin-forgot-password">Reset Password</a>
<a href="{{ url_for('admin._reset') }}" class="signin-forgot-password">Reset Password</a>
</form>
</div>
{% endblock %}

@ -3,14 +3,14 @@
{% block navbar %}
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container">
<a href="{{ url_for('admin_views.home') }}" class="navbar-brand mb-0 h1">RefTest | Admin</a>
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest | Admin</a>
</div>
</nav>
{% endblock %}
{% block content %}
<div class="form-container">
<form name="form-register" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_auth.login') }}">
<form name="form-register" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._login') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Register an Account</h2>
{{ form.hidden_tag() }}

@ -2,7 +2,7 @@
{% block content %}
<div class="form-container">
<form name="form-reset" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_auth.login') }}">
<form name="form-reset" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._login') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Reset Password</h2>
{{ form.hidden_tag() }}

@ -2,7 +2,7 @@
{% block content %}
<div class="form-container">
<form name="form-update-password" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_auth.login') }}">
<form name="form-update-password" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._login') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Update Password</h2>
{{ form.hidden_tag() }}

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container">
<a href="{{ url_for('admin_views.home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
<button
class="navbar-toggler"
type="button"
@ -14,24 +14,24 @@
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbar">
<ul class="navbar-nav">
{% if not check_login() %}
{% if not current_user.is_authenticated %}
<li class="nav-item" id="nav-login">
<a href="{{ url_for('admin_auth.login') }}" id="link-login" class="nav-link">Log In</a>
<a href="{{ url_for('admin._login') }}" id="link-login" class="nav-link">Log In</a>
</li>
{% endif %}
{% if check_login() %}
{% if current_user.is_authenticated %}
<li class="nav-item" id="nav-results">
<a href="{{ url_for('results._results') }}" id="link-results" class="nav-link">View Results</a>
<a href="{{ url_for('admin._view_entries') }}" id="link-results" class="nav-link">View Results</a>
</li>
<li class="nav-item" id="nav-tests">
<a href="{{ url_for('admin_views.tests') }}" id="link-tests" class="nav-link">Manage Exams</a>
<a href="{{ url_for('admin._tests') }}" id="link-tests" class="nav-link">Manage Exams</a>
</li>
<li class="nav-item dropdown" id="nav-settings">
<a
class="nav-link dropdown-toggle"
id="dropdown-account"
role="button"
href="{{ url_for('admin_views.settings') }}"
href="{{ url_for('admin._settings') }}"
data-bs-toggle="dropdown"
aria-expanded="false"
>
@ -39,13 +39,16 @@
</a>
<ul
class="dropdown-menu"
aria-labelledby="dropdown-account"
aria-labelledby="dropdown-settings"
>
<li>
<a href="{{ url_for('admin_views.users') }}" id="link-users" class="dropdown-item">Users</a>
<a href="{{ url_for('admin._settings') }}" id="link-settings" class="dropdown-item">Settings</a>
</li>
<li>
<a href="{{ url_for('admin_views.questions') }}" id="link-questions" class="dropdown-item">Question Datasets</a>
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
</li>
<li>
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Question Datasets</a>
</li>
</ul>
</li>
@ -54,7 +57,7 @@
class="nav-link dropdown-toggle"
id="dropdown-account"
role="button"
href="{{ url_for('admin_auth.account') }}"
href="{{ url_for('admin._update_user', id=current_user.id) }}"
data-bs-toggle="dropdown"
aria-expanded="false"
>
@ -65,10 +68,10 @@
aria-labelledby="dropdown-account"
>
<li>
<a href="{{ url_for('admin_auth.account') }}" id="link-account" class="dropdown-item">Account Settings</a>
<a href="{{ url_for('admin._update_user', id=current_user.id) }}" id="link-account" class="dropdown-item">Account Settings</a>
</li>
<li>
<a href="{{ url_for('admin_auth.logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
<a href="{{ url_for('admin._logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
</li>
</ul>
</li>

@ -3,19 +3,19 @@
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
<ul class="nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='active') }}">Active</a>
<a class="nav-link" href="{{ url_for('admin._tests', filter='active') }}">Active</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='scheduled') }}">Scheduled</a>
<a class="nav-link" href="{{ url_for('admin._tests', filter='scheduled') }}">Scheduled</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='expired') }}">Expired</a>
<a class="nav-link" href="{{ url_for('admin._tests', filter='expired') }}">Expired</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='all') }}">All</a>
<a class="nav-link" href="{{ url_for('admin._tests', filter='all') }}">All</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='create') }}">Create</a>
<a class="nav-link" href="{{ url_for('admin._tests', filter='create') }}">Create</a>
</li>
</ul>
</div>

@ -25,22 +25,22 @@
{% for test in current_tests %}
<tr>
<td>
<a href="{{ url_for('admin_views.view_test', _id=test._id) }}">{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}</a>
<a href="{{ url_for('admin._view_test', id=test.id) }}">{{ test.get_code() }}</a>
</td>
<td>
{{ test.expiry_date.strftime('%d %b %Y') }}
{{ test.end_date.strftime('%d %b %Y') }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{{ url_for('admin_views.tests', filter='active') }}" class="btn btn-primary">View Exams</a>
<a href="{{ url_for('admin._tests', filter='active') }}" class="btn btn-primary">View Exams</a>
{% else %}
<div class="alert alert-primary">
There are currently no active exams.
</div>
<a href="{{ url_for('admin_views.tests', filter='create') }}" class="btn btn-primary">Create Exam</a>
<a href="{{ url_for('admin._tests', filter='create') }}" class="btn btn-primary">Create Exam</a>
{% endif %}
</div>
</div>
@ -69,7 +69,7 @@
{% for result in recent_results %}
<tr>
<td>
<a href="{{ url_for('admin_views.view_entry', _id=result._id) }}">{{ result.name.surname }}, {{ result.name.first_name }}</a>
<a href="{{ url_for('admin._view_entry', id=result.id) }}">{{ result.name.surname }}, {{ result.name.first_name }}</a>
</td>
<td>
{{ result.submission_time.strftime('%d %b %Y %H:%M') }}
@ -82,7 +82,7 @@
</tbody>
</table>
</div>
<a href="{{ url_for('admin_views.view_entries') }}" class="btn btn-primary">View Results</a>
<a href="{{ url_for('admin._view_entries') }}" class="btn btn-primary">View Results</a>
{% else %}
<div class="alert alert-primary">
There are currently no exam results to preview.
@ -114,22 +114,22 @@
{% for test in upcoming_tests %}
<tr>
<td>
<a href="{{ url_for('admin_views.view_test', _id=test._id) }}">{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}</a>
<a href="{{ url_for('admin._view_test', id=test.id) }}">{{ test.get_code() }}</a>
</td>
<td>
{{ test.expiry_date.strftime('%d %b %Y') }}
{{ test.end_date.strftime('%d %b %Y') }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{{ url_for('admin_views.tests', filter='scheduled') }}" class="btn btn-primary">View Exams</a>
<a href="{{ url_for('admin._tests', filter='scheduled') }}" class="btn btn-primary">View Exams</a>
{% else %}
<div class="alert alert-primary">
There are currently no upcoming exams.
</div>
<a href="{{ url_for('admin_views.tests', filter='create') }}" class="btn btn-primary">Create Exam</a>
<a href="{{ url_for('admin._tests', filter='create') }}" class="btn btn-primary">Create Exam</a>
{% endif %}
</div>
</div>

@ -164,19 +164,19 @@
{% endif %}
<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 }}">
<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 }}">
<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 }}">
<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>

@ -69,9 +69,9 @@
</td>
<td class="row-actions">
<a
href="{{ url_for('admin_views.view_entry', _id = entry._id ) }}"
href="{{ url_for('admin._view_entry', id = entry.id ) }}"
class="btn btn-primary entry-details"
data-_id="{{entry._id}}"
data-id="{{entry.id}}"
title="View Details"
>
<i class="bi bi-file-medical-fill button-icon"></i>

@ -2,11 +2,11 @@
{% block content %}
<div class="form-container">
<form name="form-delete-user" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_views.users') }}">
<form name="form-delete-user" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._users') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Delete User &lsquo;{{ user.username }}&rsquo;?</h2>
<h2 class="form-heading">Delete User &lsquo;{{ user.get_username() }}&rsquo;?</h2>
{{ form.hidden_tag() }}
<p>This action cannot be undone. Deleting an account will mean {{ user.username }} will no longer be able to log in to the admin console.</p>
<p>This action cannot be undone. Deleting an account will mean {{ user.get_username() }} will no longer be able to log in to the admin console.</p>
<p>Are you sure you want to proceed?</p>
<div class="form-label-group">
{{ form.password(class_="form-control", placeholder="Confirm Your Password", autofocus=true) }}
@ -20,7 +20,7 @@
<div class="container form-submission-button">
<div class="row">
<div class="col text-center">
<a href="{{ url_for('admin_views.users') }}" autofocus="true" class="btn btn-md btn-primary btn-block" type="button">
<a href="{{ url_for('admin._users') }}" autofocus="true" class="btn btn-md btn-primary btn-block" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
</svg>

@ -28,22 +28,22 @@
<tr>
<td>
<a href="
{% if user._id == get_id_from_cookie() %}
{{ url_for('admin_auth.account') }}
{% if user == current_user %}
{{ url_for('admin._update_user', id=current_user.id) }}
{% else %}
{{ url_for('admin_views.update_user', _id=user._id) }}
{{ url_for('admin._update_user', id=user.id) }}
{% endif%}
">{{ user.username }}</a>
">{{ user.get_username() }}</a>
</td>
<td>
<a href="mailto:{{ user.email }}">{{ user.email }}</a>
<a href="mailto:{{ user.get_email() }}">{{ user.get_email() }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{{ url_for('admin_views.users') }}" class="btn btn-primary">Manage Users</a>
<a href="{{ url_for('admin._users') }}" class="btn btn-primary">Manage Users</a>
</div>
</div>
</div>
@ -57,7 +57,7 @@
<thead>
<tr>
<th>
File Name
Uploaded
</th>
<th>
Exams
@ -68,22 +68,22 @@
{% for dataset in datasets %}
<tr>
<td>
{{ dataset.filename }}
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
</td>
<td>
{{ dataset.use }}
{{ dataset.tests|length }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{{ url_for('admin_views.questions') }}" class="btn btn-primary">Manage Datasets</a>
<a href="{{ url_for('admin._questions') }}" class="btn btn-primary">Manage Datasets</a>
{% else %}
<div class="alert alert-primary">
There are currently no question datasets uploaded.
</div>
<a href="{{ url_for('admin_views.questions') }}" class="btn btn-primary">Upload Dataset</a>
<a href="{{ url_for('admin._questions') }}" class="btn btn-primary">Upload Dataset</a>
{% endif %}
</div>
</div>

@ -9,9 +9,6 @@
<tr>
<th>
</th>
<th data-priority="1">
File Name
</th>
<th data-priority="2">
Uploaded
@ -31,7 +28,7 @@
{% for element in data %}
<tr class="table-row">
<td>
{% if element.filename == default %}
{% if element.default %}
<div class="text-success" title="Default Dataset">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi success bi-caret-right-fill" viewBox="0 0 16 16">
<path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
@ -40,16 +37,13 @@
{% endif %}
</td>
<td>
{{ element.filename }}
{{ element.date.strftime('%d %b %Y %H:%M') }}
</td>
<td>
{{ element.timestamp.strftime('%d %b %Y') }}
{{ element.creator.get_username() }}
</td>
<td>
{{ element.author }}
</td>
<td>
{{ element.use }}
{{ element.tests|length }}
</td>
<td class="row-actions">
<a
@ -112,10 +106,10 @@
$(document).ready(function() {
$('#question-datasets-table').DataTable({
'columnDefs': [
{'sortable': false, 'targets': [0,5]},
{'searchable': false, 'targets': [0,4,5]}
{'sortable': false, 'targets': [0,4]},
{'searchable': false, 'targets': [0,3,4]}
],
'order': [[2, 'desc'], [3, 'asc']],
'order': [[1, 'desc'], [2, 'asc']],
'responsive': 'true',
'fixedHeader': 'true',
});

@ -2,12 +2,12 @@
{% block content %}
<div class="form-container">
<form name="form-update-user" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin_views.users') }}">
<form name="form-update-user" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._users') }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Update User &lsquo;{{ user.username }}&rsquo;</h2>
<h2 class="form-heading">Update User &lsquo;{{ user.get_username() }}&rsquo;</h2>
{{ form.hidden_tag() }}
<div class="form-label-group">
{{ form.email(class_="form-control", placeholder="Email Address", value = user.email) }}
{{ form.email(class_="form-control", placeholder="Email Address", value = user.get_email()) }}
{{ form.email.label }}
</div>
<div class="form-label-group">
@ -23,17 +23,17 @@
{{ form.notify.label }}
</div>
<div class="form-label-group">
Please confirm <strong>your password</strong> before committing any changes to a user account.
Please confirm <strong>your current password</strong> before committing any changes to a user account.
</div>
<div class="form-label-group">
{{ form.user_password(class_="form-control", placeholder="Your Password", value = user.email) }}
{{ form.user_password.label }}
{{ form.confirm_password(class_="form-control", placeholder="Your Password", value = user.email) }}
{{ form.confirm_password.label }}
</div>
{% include "admin/components/client-alerts.html" %}
<div class="container form-submission-button">
<div class="row">
<div class="col text-center">
<a href="{{ url_for('admin_views.users') }}" class="btn btn-md btn-danger btn-block" type="button">
<a href="{{ url_for('admin._users') }}" class="btn btn-md btn-danger btn-block" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
</svg>

@ -23,7 +23,7 @@
{% for user in users %}
<tr class="table-row">
<td>
{% if user._id == get_id_from_cookie() %}
{% if user == current_user %}
<div class="text-success" title="Current User">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi success bi-caret-right-fill" viewBox="0 0 16 16">
<path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
@ -32,18 +32,18 @@
{% endif %}
</td>
<td>
{{ user.username }}
{{ user.get_username() }}
</td>
<td>
{{ user.email }}
{{ user.get_email() }}
</td>
<td class="row-actions">
<a
href="
{% if not user._id == get_id_from_cookie() %}
{{ url_for('admin_views.update_user', _id = user._id ) }}
{% if not user == current_user %}
{{ url_for('admin._update_user', id = user.id ) }}
{% else %}
{{ url_for('admin_auth.account') }}
{{ url_for('admin._update_user', id=current_user.id) }}
{% endif %}
"
class="btn btn-primary"
@ -53,15 +53,15 @@
</a>
<a
href="
{% if not user._id == get_id_from_cookie() %}
{{ url_for('admin_views.delete_user', _id = user._id ) }}
{% if not user == current_user %}
{{ url_for('admin._delete_user', id = user.id ) }}
{% else %}
#
{% endif %}
"
class="btn btn-danger {% if user._id == get_id_from_cookie() %} disabled {% endif %}"
class="btn btn-danger {% if user == current_user %} disabled {% endif %}"
title="Delete User"
{% if user._id == get_id_from_cookie() %} onclick="return false" {% endif %}
{% if user == current_user %} onclick="return false" {% endif %}
>
<i class="bi bi-person-x-fill button-icon"></i>
</button>

@ -12,38 +12,33 @@
<h5 class="mb-1">Exam Code</h5>
</div>
<h2>
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
{{ test.get_code() }}
</h2>
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Dataset</h5>
</div>
{{ test.dataset }}
{{ test.dataset.date.strftime('%Y%m%d%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">Created By</h5>
</div>
{{ test.creator }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Date Created</h5>
</div>
{{ test.date_created.strftime('%d %b %Y') }}
{{ test.creator.get_username() }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Start Date</h5>
</div>
{{ test.start_date.strftime('%d %b %Y') }}
{{ test.start_date.strftime('%d %b %Y %H:%M') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Expiry Date</h5>
</div>
{{ test.expiry_date.strftime('%d %b %Y') }}
{{ test.end_date.strftime('%d %b %Y %H:%M') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
@ -62,7 +57,7 @@
{% endif %}
</li>
<div class="accordion" id="test-info-detail">
{% if 'entries' in test and test.entries|length > 0 %}
{% if test.entries|length > 0 %}
<div class="accordion-item">
<h2 class="accordion-header" id="test-entries">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-entries-list" aria-expanded="false" aria-controls="test-entries-list">
@ -76,7 +71,7 @@
{% for entry in test.entries %}
<tr>
<td>
<a href="{{ url_for('admin_views.view_entry', _id=entry) }}" >Entry {{ loop.index }}</a>
<a href="{{ url_for('admin._view_entry', id=entry) }}" >Entry {{ loop.index }}</a>
</td>
</tr>
{% endfor %}
@ -86,7 +81,7 @@
</div>
</div>
{% endif %}
{% if 'time_adjustments' in test and test.time_adjustments|length > 0 %}
{% if test.adjustments %}
<div class="accordion-item">
<h2 class="accordion-header" id="test-adjustments">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-adjustments-list" aria-expanded="false" aria-controls="test-adjustments-list">
@ -110,10 +105,10 @@
</tr>
</thead>
<tbody>
{% for key, value in test.time_adjustments.items() %}
{% for key, value in test.adjustments.items() %}
<tr>
<td>
{{ key }}
{{ key.upper() }}
</td>
<td>
{{ value }}
@ -143,7 +138,7 @@
<form name="form-add-adjustment" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="">
{{ form.hidden_tag() }}
<div class="form-label-group">
{{ form.time(class_="form-control", placeholder="Enter Username") }}
{{ form.time(class_="form-control", placeholder="Enter Time") }}
{{ form.time.label }}
</div>
<div class="container form-submission-button">
@ -168,11 +163,18 @@
</div>
<div class="container justify-content-center">
<div class="row">
<a href="#" class="btn btn-warning test-action" data-action="close" data-_id="{{ test._id }}">
<i class="bi bi-hourglass button-icon"></i>
Close Exam
</a>
<a href="#" class="btn btn-danger test-action" data-action="delete" data-_id="{{ test._id }}">
{% if test.start_date <= now %}
<a href="#" class="btn btn-warning test-action {% if test.end_date < now %}disabled{% endif %}" data-action="end" data-id="{{ test.id }}">
<i class="bi bi-hourglass-bottom button-icon"></i>
Close Exam
</a>
{% else %}
<a href="#" class="btn btn-success test-action {% if test.start_date < now %}disabled{% endif %}" data-action="start" data-id="{{ test.id }}">
<i class="bi bi-hourglass-top button-icon"></i>
Start Exam
</a>
{% endif %}
<a href="#" class="btn btn-danger test-action" data-action="delete" data-id="{{ test.id }}">
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
Delete Exam
</a>

@ -33,13 +33,13 @@
{% for test in tests %}
<tr class="table-row">
<td>
{{ test.start_date.strftime('%d %b %Y') }}
{{ test.start_date.strftime('%d %b %y %H:%M') }}
</td>
<td>
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
{{ test.get_code() }}
</td>
<td>
{{ test.expiry_date.strftime('%d %b %Y') }}
{{ test.end_date.strftime('%d %b %Y %H:%M') }}
</td>
<td>
{% if test.time_limit == None -%}
@ -61,7 +61,7 @@
<a
href="#"
class="btn btn-primary test-action"
data-_id="{{test._id}}"
data-id="{{test.id}}"
title="Edit Exam"
data-action="edit"
>
@ -70,7 +70,7 @@
<a
href="#"
class="btn btn-danger test-action"
data-_id="{{test._id}}"
data-id="{{test.id}}"
title="Delete Exam"
data-action="delete"
>

@ -9,7 +9,7 @@ from flask import Blueprint, jsonify, render_template, redirect, request, sessio
from flask.helpers import flash, url_for
from flask_login import current_user, login_required
from datetime import date, datetime
from datetime import date, datetime, timedelta
from json import loads
import secrets
@ -27,12 +27,12 @@ admin = Blueprint(
def _home():
tests = Test.query.all()
results = Entry.query.all()
current_tests = [ test for test in tests if test['expiry_date'].date() >= datetime.now().date() and test['start_date'].date() <= date.today() ]
current_tests.sort(key= lambda x: x['expiry_date'], reverse=True)
upcoming_tests = [ test for test in tests if test['start_date'].date() > datetime.now().date()]
upcoming_tests.sort(key= lambda x: x['start_date'])
recent_results = [result for result in results if not result['status'] == 'started' ]
recent_results.sort(key= lambda x: x['end_time'], reverse=True)
current_tests = [ test for test in tests if test.end_date >= datetime.now() and test.start_date.date() <= date.today() ]
current_tests.sort(key= lambda x: x.end_date, reverse=True)
upcoming_tests = [ test for test in tests if test.start_date.date() > datetime.now().date()]
upcoming_tests.sort(key= lambda x: x.start_date)
recent_results = [result for result in results if not result.status == 'started' ]
recent_results.sort(key= lambda x: x.end_time, reverse=True)
for result in recent_results:
result['percent'] = round(100*result['result']['score']/result['result']['max'])
return render_template('/admin/index.html', current_tests = current_tests, upcomimg_tests = upcoming_tests, recent_results = recent_results)
@ -83,9 +83,9 @@ def _register():
if request.method == 'POST':
if form.validate_on_submit():
new_user = User()
new_user.set_username = request.form.get('username').lower()
new_user.set_email = request.form.get('email').lower()
new_user.set_password = request.form.get('password').lower()
new_user.set_username(request.form.get('username').lower())
new_user.set_email(request.form.get('email').lower())
new_user.set_password(request.form.get('password'))
success, message = new_user.register()
if success:
flash(message=f'{message} Please log in to continue.', category='success')
@ -95,7 +95,7 @@ def _register():
return jsonify({'error': message}), 401
errors = [*form.username.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
return jsonify({ 'error': errors}), 400
return render_template('admin/auth/register.html')
return render_template('admin/auth/register.html', form=form)
@admin.route('/reset/')
def _reset():
@ -149,9 +149,9 @@ def _users():
if form.validate_on_submit():
password = request.form.get('password')
new_user = User()
new_user.set_username = request.form.get('username').lower()
new_user.set_password = secrets.token_hex(12) if not password else password
new_user.set_email = request.form.get('email')
new_user.set_username(request.form.get('username').lower())
new_user.set_password(secrets.token_hex(12)) if not password else password
new_user.set_email(request.form.get('email'))
success, message = new_user.register(notify=request.form.get('notify'))
if success: return jsonify({'success': message}), 200
return jsonify({'error': message}), 401
@ -192,23 +192,26 @@ def _update_user(id:str):
if request.method == 'POST':
if not user: return jsonify({'error': 'User does not exist.'}), 400
if form.validate_on_submit():
if not user.verify_password(request.form.get('confirm_password')): return jsonify({'error': 'Invalid password for your account.'}), 401
success, message = user.update(
password = request.form.get('password'),
email = request.form.get('email'),
notify = request.form.get('notify')
)
if success: return jsonify({'success': message}), 200
if success:
flash(message, 'success')
return jsonify({'success': message}), 200
return jsonify({'error': message}), 400
errors = [*form.confirm_password.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
return jsonify({ 'error': errors}), 400
if not user:
flash('User not found.', 'error')
return redirect(url_for('admin._users'))
return render_template('/admin/settings/delete_user.html', form=form, id = id, user = user)
return render_template('/admin/settings/update_user.html', form=form, id = id, user = user)
@admin.route('/settings/questions/', methods=['GET', 'POST'])
@login_required
def _quesitons():
def _questions():
form = UploadData()
if request.method == 'POST':
if form.validate_on_submit():
@ -226,7 +229,7 @@ def _quesitons():
return jsonify({ 'error': errors}), 400
data = Dataset.query.all()
return render_template('/admin/settings/questions.html', data=data)
return render_template('/admin/settings/questions.html', form=form, data=data)
@admin.route('/settings/questions/edit/', methods=['POST'])
@login_required
@ -248,6 +251,7 @@ def _tests(filter:str=None):
tests = None
_tests = Test.query.all()
form = None
now = datetime.now()
if not datasets:
flash('There are no available question datasets. Please upload a question dataset in order to set up an exam.', 'error')
return redirect(url_for('admin._questions'))
@ -255,21 +259,21 @@ def _tests(filter:str=None):
if filter == 'create':
form = CreateTest()
form.time_limit.choices = get_time_options()
form.dataset.choices = get_dataset_choices
form.dataset.choices = get_dataset_choices()
form.time_limit.default='none'
form.process()
display_title = ''
error_none = ''
if filter in [None, '', 'active']:
tests = [ test for test in _tests if test['expiry_date'].date() >= date.today() and test['start_date'].date() <= date.today() ]
tests = [ test for test in _tests if test.end_date >= now and test.start_date <= now ]
display_title = 'Active Exams'
error_none = 'There are no exams that are currently active. You can create one using the Creat Exam form.'
if filter == 'expired':
tests = [ test for test in _tests if test['expiry_date'].date() < date.today() ]
tests = [ test for test in _tests if test.end_date < now ]
display_title = 'Expired Exams'
error_none = 'There are no expired exams. Exams will appear in this category after their expiration date has passed.'
if filter == 'scheduled':
tests = [ test for test in _tests if test['start_date'].date() > date.today()]
tests = [ test for test in _tests if test.start_date > now]
display_title = 'Scheduled Exams'
error_none = 'There are no scheduled exams pending. You can schedule an exam for the future using the Create Exam form.'
if filter == 'all':
@ -287,11 +291,12 @@ def _create_test():
if form.validate_on_submit():
new_test = Test()
new_test.start_date = request.form.get('start_date')
new_test.start_date = datetime.strptime(new_test.start_date, '%Y-%m-%d')
new_test.start_date = datetime.strptime(new_test.start_date, '%Y-%m-%dT%H:%M')
new_test.end_date = request.form.get('expiry_date')
new_test.end_date = datetime.strptime(new_test.end_date, '%Y-%m-%d')
new_test.end_date = datetime.strptime(new_test.end_date, '%Y-%m-%dT%H:%M')
new_test.time_limit = request.form.get('time_limit')
dataset = request.form.get('dataset')
new_test.dataset = Dataset.query.filter_by(id=dataset)
new_test.dataset = Dataset.query.filter_by(id=dataset).first()
success, message = new_test.create()
if success:
flash(message=message, category='success')
@ -371,7 +376,7 @@ def _view_entry(id:str=None):
flash('Invalid entry ID.', 'error')
return redirect(url_for('admin._view_entries'))
test = entry['test']
dataset = test['dataset']
dataset = test.dataset
dataset_path = dataset.get_file()
with open(dataset_path, 'r') as _dataset:
data = loads(_dataset.read())