Finished admin console
This commit is contained in:
parent
62160beab2
commit
2ea778143e
@ -70,14 +70,14 @@ $('form[name=form-upload-questions]').submit(function(event) {
|
|||||||
// Edit and Delete Test Button Handlers
|
// Edit and Delete Test Button Handlers
|
||||||
$('.test-action').click(function(event) {
|
$('.test-action').click(function(event) {
|
||||||
|
|
||||||
let _id = $(this).data('_id');
|
let id = $(this).data('id');
|
||||||
let action = $(this).data('action');
|
let action = $(this).data('action');
|
||||||
|
|
||||||
if (action == 'delete') {
|
if (action == 'delete' || action == 'start' || action == 'end') {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/admin/tests/delete/`,
|
url: `/admin/tests/edit/`,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({'_id': _id}),
|
data: JSON.stringify({'id': id, 'action': action}), // TODO Change how CRUD operations work
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.href = '/admin/tests/';
|
window.location.href = '/admin/tests/';
|
||||||
@ -87,21 +87,7 @@ $('.test-action').click(function(event) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (action == 'edit') {
|
} else if (action == 'edit') {
|
||||||
window.location.href = `/admin/test/${_id}/`
|
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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -185,13 +171,13 @@ $('#dismiss-cookie-alert').click(function(event){
|
|||||||
// Script for Result Actions
|
// Script for Result Actions
|
||||||
$('.result-action-buttons').click(function(event){
|
$('.result-action-buttons').click(function(event){
|
||||||
|
|
||||||
var _id = $(this).data('_id');
|
var id = $(this).data('id');
|
||||||
|
|
||||||
if ($(this).data('result-action') == 'generate') {
|
if ($(this).data('result-action') == 'generate') {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/certificate/',
|
url: '/admin/certificate/',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({'_id': _id}),
|
data: JSON.stringify({'id': id}),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'html',
|
dataType: 'html',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@ -207,7 +193,7 @@ $('.result-action-buttons').click(function(event){
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({'_id': _id, 'action': action}),
|
data: JSON.stringify({'id': id, 'action': action}),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (action == 'delete') {
|
if (action == 'delete') {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Update Your Account</h2>
|
<h2 class="form-heading">Update Your Account</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<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">
|
||||||
<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">
|
<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"/>
|
<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>
|
</svg>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form">Log In</h2>
|
<h2 class="form">Log In</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -3,14 +3,14 @@
|
|||||||
{% block navbar %}
|
{% block navbar %}
|
||||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
||||||
<div class="container">
|
<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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Register an Account</h2>
|
<h2 class="form-heading">Register an Account</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Reset Password</h2>
|
<h2 class="form-heading">Reset Password</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Update Password</h2>
|
<h2 class="form-heading">Update Password</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
||||||
<div class="container">
|
<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
|
<button
|
||||||
class="navbar-toggler"
|
class="navbar-toggler"
|
||||||
type="button"
|
type="button"
|
||||||
@ -14,24 +14,24 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse justify-content-end" id="navbar">
|
<div class="collapse navbar-collapse justify-content-end" id="navbar">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% if not check_login() %}
|
{% if not current_user.is_authenticated %}
|
||||||
<li class="nav-item" id="nav-login">
|
<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>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if check_login() %}
|
{% if current_user.is_authenticated %}
|
||||||
<li class="nav-item" id="nav-results">
|
<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>
|
||||||
<li class="nav-item" id="nav-tests">
|
<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>
|
||||||
<li class="nav-item dropdown" id="nav-settings">
|
<li class="nav-item dropdown" id="nav-settings">
|
||||||
<a
|
<a
|
||||||
class="nav-link dropdown-toggle"
|
class="nav-link dropdown-toggle"
|
||||||
id="dropdown-account"
|
id="dropdown-account"
|
||||||
role="button"
|
role="button"
|
||||||
href="{{ url_for('admin_views.settings') }}"
|
href="{{ url_for('admin._settings') }}"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
@ -39,13 +39,16 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
class="dropdown-menu"
|
class="dropdown-menu"
|
||||||
aria-labelledby="dropdown-account"
|
aria-labelledby="dropdown-settings"
|
||||||
>
|
>
|
||||||
<li>
|
<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>
|
||||||
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -54,7 +57,7 @@
|
|||||||
class="nav-link dropdown-toggle"
|
class="nav-link dropdown-toggle"
|
||||||
id="dropdown-account"
|
id="dropdown-account"
|
||||||
role="button"
|
role="button"
|
||||||
href="{{ url_for('admin_auth.account') }}"
|
href="{{ url_for('admin._update_user', id=current_user.id) }}"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
@ -65,10 +68,10 @@
|
|||||||
aria-labelledby="dropdown-account"
|
aria-labelledby="dropdown-account"
|
||||||
>
|
>
|
||||||
<li>
|
<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>
|
||||||
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
|
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,22 +25,22 @@
|
|||||||
{% for test in current_tests %}
|
{% for test in current_tests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
{{ test.expiry_date.strftime('%d %b %Y') }}
|
{{ test.end_date.strftime('%d %b %Y') }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% else %}
|
||||||
<div class="alert alert-primary">
|
<div class="alert alert-primary">
|
||||||
There are currently no active exams.
|
There are currently no active exams.
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
{% for result in recent_results %}
|
{% for result in recent_results %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
{{ result.submission_time.strftime('%d %b %Y %H:%M') }}
|
{{ result.submission_time.strftime('%d %b %Y %H:%M') }}
|
||||||
@ -82,7 +82,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% else %}
|
||||||
<div class="alert alert-primary">
|
<div class="alert alert-primary">
|
||||||
There are currently no exam results to preview.
|
There are currently no exam results to preview.
|
||||||
@ -114,22 +114,22 @@
|
|||||||
{% for test in upcoming_tests %}
|
{% for test in upcoming_tests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
{{ test.expiry_date.strftime('%d %b %Y') }}
|
{{ test.end_date.strftime('%d %b %Y') }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% else %}
|
||||||
<div class="alert alert-primary">
|
<div class="alert alert-primary">
|
||||||
There are currently no upcoming exams.
|
There are currently no upcoming exams.
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,19 +164,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="container justify-content-center">
|
<div class="container justify-content-center">
|
||||||
<div class="row">
|
<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>
|
<i class="bi bi-printer-fill button-icon"></i>
|
||||||
Printable Version
|
Printable Version
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if entry.status == 'late' %}
|
{% 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>
|
<i class="bi bi-clock-history button-icon"></i>
|
||||||
Allow Late Entry
|
Allow Late Entry
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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>
|
<i class="bi bi-trash-fill button-icon"></i>
|
||||||
Delete Result
|
Delete Result
|
||||||
</a>
|
</a>
|
||||||
|
@ -69,9 +69,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="row-actions">
|
<td class="row-actions">
|
||||||
<a
|
<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"
|
class="btn btn-primary entry-details"
|
||||||
data-_id="{{entry._id}}"
|
data-id="{{entry.id}}"
|
||||||
title="View Details"
|
title="View Details"
|
||||||
>
|
>
|
||||||
<i class="bi bi-file-medical-fill button-icon"></i>
|
<i class="bi bi-file-medical-fill button-icon"></i>
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Delete User ‘{{ user.username }}’?</h2>
|
<h2 class="form-heading">Delete User ‘{{ user.get_username() }}’?</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ 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>
|
<p>Are you sure you want to proceed?</p>
|
||||||
<div class="form-label-group">
|
<div class="form-label-group">
|
||||||
{{ form.password(class_="form-control", placeholder="Confirm Your Password", autofocus=true) }}
|
{{ form.password(class_="form-control", placeholder="Confirm Your Password", autofocus=true) }}
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<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">
|
||||||
<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">
|
<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"/>
|
<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>
|
</svg>
|
@ -28,22 +28,22 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="
|
<a href="
|
||||||
{% if user._id == get_id_from_cookie() %}
|
{% if user == current_user %}
|
||||||
{{ url_for('admin_auth.account') }}
|
{{ url_for('admin._update_user', id=current_user.id) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ url_for('admin_views.update_user', _id=user._id) }}
|
{{ url_for('admin._update_user', id=user.id) }}
|
||||||
{% endif%}
|
{% endif%}
|
||||||
">{{ user.username }}</a>
|
">{{ user.get_username() }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="mailto:{{ user.email }}">{{ user.email }}</a>
|
<a href="mailto:{{ user.get_email() }}">{{ user.get_email() }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
File Name
|
Uploaded
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Exams
|
Exams
|
||||||
@ -68,22 +68,22 @@
|
|||||||
{% for dataset in datasets %}
|
{% for dataset in datasets %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{ dataset.filename }}
|
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ dataset.use }}
|
{{ dataset.tests|length }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% else %}
|
||||||
<div class="alert alert-primary">
|
<div class="alert alert-primary">
|
||||||
There are currently no question datasets uploaded.
|
There are currently no question datasets uploaded.
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,9 +9,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
|
||||||
</th>
|
|
||||||
<th data-priority="1">
|
|
||||||
File Name
|
|
||||||
</th>
|
</th>
|
||||||
<th data-priority="2">
|
<th data-priority="2">
|
||||||
Uploaded
|
Uploaded
|
||||||
@ -31,7 +28,7 @@
|
|||||||
{% for element in data %}
|
{% for element in data %}
|
||||||
<tr class="table-row">
|
<tr class="table-row">
|
||||||
<td>
|
<td>
|
||||||
{% if element.filename == default %}
|
{% if element.default %}
|
||||||
<div class="text-success" title="Default Dataset">
|
<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">
|
<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"/>
|
<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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ element.filename }}
|
{{ element.date.strftime('%d %b %Y %H:%M') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ element.timestamp.strftime('%d %b %Y') }}
|
{{ element.creator.get_username() }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ element.author }}
|
{{ element.tests|length }}
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ element.use }}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="row-actions">
|
<td class="row-actions">
|
||||||
<a
|
<a
|
||||||
@ -112,10 +106,10 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#question-datasets-table').DataTable({
|
$('#question-datasets-table').DataTable({
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{'sortable': false, 'targets': [0,5]},
|
{'sortable': false, 'targets': [0,4]},
|
||||||
{'searchable': false, 'targets': [0,4,5]}
|
{'searchable': false, 'targets': [0,3,4]}
|
||||||
],
|
],
|
||||||
'order': [[2, 'desc'], [3, 'asc']],
|
'order': [[1, 'desc'], [2, 'asc']],
|
||||||
'responsive': 'true',
|
'responsive': 'true',
|
||||||
'fixedHeader': 'true',
|
'fixedHeader': 'true',
|
||||||
});
|
});
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form-container">
|
<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" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form-heading">Update User ‘{{ user.username }}’</h2>
|
<h2 class="form-heading">Update User ‘{{ user.get_username() }}’</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<div class="form-label-group">
|
<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 }}
|
{{ form.email.label }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-label-group">
|
<div class="form-label-group">
|
||||||
@ -23,17 +23,17 @@
|
|||||||
{{ form.notify.label }}
|
{{ form.notify.label }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-label-group">
|
<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>
|
||||||
<div class="form-label-group">
|
<div class="form-label-group">
|
||||||
{{ form.user_password(class_="form-control", placeholder="Your Password", value = user.email) }}
|
{{ form.confirm_password(class_="form-control", placeholder="Your Password", value = user.email) }}
|
||||||
{{ form.user_password.label }}
|
{{ form.confirm_password.label }}
|
||||||
</div>
|
</div>
|
||||||
{% include "admin/components/client-alerts.html" %}
|
{% include "admin/components/client-alerts.html" %}
|
||||||
<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">
|
||||||
<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">
|
<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"/>
|
<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>
|
</svg>
|
@ -23,7 +23,7 @@
|
|||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr class="table-row">
|
<tr class="table-row">
|
||||||
<td>
|
<td>
|
||||||
{% if user._id == get_id_from_cookie() %}
|
{% if user == current_user %}
|
||||||
<div class="text-success" title="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">
|
<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"/>
|
<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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.username }}
|
{{ user.get_username() }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.get_email() }}
|
||||||
</td>
|
</td>
|
||||||
<td class="row-actions">
|
<td class="row-actions">
|
||||||
<a
|
<a
|
||||||
href="
|
href="
|
||||||
{% if not user._id == get_id_from_cookie() %}
|
{% if not user == current_user %}
|
||||||
{{ url_for('admin_views.update_user', _id = user._id ) }}
|
{{ url_for('admin._update_user', id = user.id ) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ url_for('admin_auth.account') }}
|
{{ url_for('admin._update_user', id=current_user.id) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
@ -53,15 +53,15 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="
|
href="
|
||||||
{% if not user._id == get_id_from_cookie() %}
|
{% if not user == current_user %}
|
||||||
{{ url_for('admin_views.delete_user', _id = user._id ) }}
|
{{ url_for('admin._delete_user', id = user.id ) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
#
|
#
|
||||||
{% endif %}
|
{% 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"
|
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>
|
<i class="bi bi-person-x-fill button-icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -12,38 +12,33 @@
|
|||||||
<h5 class="mb-1">Exam Code</h5>
|
<h5 class="mb-1">Exam Code</h5>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<h2>
|
||||||
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
|
{{ test.get_code() }}
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item list-group-item-action">
|
<li class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">Dataset</h5>
|
<h5 class="mb-1">Dataset</h5>
|
||||||
</div>
|
</div>
|
||||||
{{ test.dataset }}
|
{{ test.dataset.date.strftime('%Y%m%d%H%M%S') }}
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item list-group-item-action">
|
<li class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">Created By</h5>
|
<h5 class="mb-1">Created By</h5>
|
||||||
</div>
|
</div>
|
||||||
{{ test.creator }}
|
{{ 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">Date Created</h5>
|
|
||||||
</div>
|
|
||||||
{{ test.date_created.strftime('%d %b %Y') }}
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="list-group-item list-group-item-action">
|
<li class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">Start Date</h5>
|
<h5 class="mb-1">Start Date</h5>
|
||||||
</div>
|
</div>
|
||||||
{{ test.start_date.strftime('%d %b %Y') }}
|
{{ test.start_date.strftime('%d %b %Y %H:%M') }}
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item list-group-item-action">
|
<li class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">Expiry Date</h5>
|
<h5 class="mb-1">Expiry Date</h5>
|
||||||
</div>
|
</div>
|
||||||
{{ test.expiry_date.strftime('%d %b %Y') }}
|
{{ test.end_date.strftime('%d %b %Y %H:%M') }}
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item list-group-item-action">
|
<li class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
@ -62,7 +57,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<div class="accordion" id="test-info-detail">
|
<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">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="test-entries">
|
<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">
|
<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 %}
|
{% for entry in test.entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -86,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'time_adjustments' in test and test.time_adjustments|length > 0 %}
|
{% if test.adjustments %}
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="test-adjustments">
|
<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">
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for key, value in test.time_adjustments.items() %}
|
{% for key, value in test.adjustments.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{ key }}
|
{{ key.upper() }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ value }}
|
{{ 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 name="form-add-adjustment" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<div class="form-label-group">
|
<div class="form-label-group">
|
||||||
{{ form.time(class_="form-control", placeholder="Enter Username") }}
|
{{ form.time(class_="form-control", placeholder="Enter Time") }}
|
||||||
{{ form.time.label }}
|
{{ form.time.label }}
|
||||||
</div>
|
</div>
|
||||||
<div class="container form-submission-button">
|
<div class="container form-submission-button">
|
||||||
@ -168,11 +163,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="container justify-content-center">
|
<div class="container justify-content-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a href="#" class="btn btn-warning test-action" data-action="close" data-_id="{{ test._id }}">
|
{% if test.start_date <= now %}
|
||||||
<i class="bi bi-hourglass button-icon"></i>
|
<a href="#" class="btn btn-warning test-action {% if test.end_date < now %}disabled{% endif %}" data-action="end" data-id="{{ test.id }}">
|
||||||
Close Exam
|
<i class="bi bi-hourglass-bottom button-icon"></i>
|
||||||
</a>
|
Close Exam
|
||||||
<a href="#" class="btn btn-danger test-action" data-action="delete" data-_id="{{ test._id }}">
|
</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>
|
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
||||||
Delete Exam
|
Delete Exam
|
||||||
</a>
|
</a>
|
||||||
|
@ -33,13 +33,13 @@
|
|||||||
{% for test in tests %}
|
{% for test in tests %}
|
||||||
<tr class="table-row">
|
<tr class="table-row">
|
||||||
<td>
|
<td>
|
||||||
{{ test.start_date.strftime('%d %b %Y') }}
|
{{ test.start_date.strftime('%d %b %y %H:%M') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
|
{{ test.get_code() }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ test.expiry_date.strftime('%d %b %Y') }}
|
{{ test.end_date.strftime('%d %b %Y %H:%M') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if test.time_limit == None -%}
|
{% if test.time_limit == None -%}
|
||||||
@ -61,7 +61,7 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-primary test-action"
|
class="btn btn-primary test-action"
|
||||||
data-_id="{{test._id}}"
|
data-id="{{test.id}}"
|
||||||
title="Edit Exam"
|
title="Edit Exam"
|
||||||
data-action="edit"
|
data-action="edit"
|
||||||
>
|
>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-danger test-action"
|
class="btn btn-danger test-action"
|
||||||
data-_id="{{test._id}}"
|
data-id="{{test.id}}"
|
||||||
title="Delete Exam"
|
title="Delete Exam"
|
||||||
data-action="delete"
|
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.helpers import flash, url_for
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, timedelta
|
||||||
from json import loads
|
from json import loads
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
@ -27,12 +27,12 @@ admin = Blueprint(
|
|||||||
def _home():
|
def _home():
|
||||||
tests = Test.query.all()
|
tests = Test.query.all()
|
||||||
results = Entry.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 = [ 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['expiry_date'], reverse=True)
|
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 = [ test for test in tests if test.start_date.date() > datetime.now().date()]
|
||||||
upcoming_tests.sort(key= lambda x: x['start_date'])
|
upcoming_tests.sort(key= lambda x: x.start_date)
|
||||||
recent_results = [result for result in results if not result['status'] == 'started' ]
|
recent_results = [result for result in results if not result.status == 'started' ]
|
||||||
recent_results.sort(key= lambda x: x['end_time'], reverse=True)
|
recent_results.sort(key= lambda x: x.end_time, reverse=True)
|
||||||
for result in recent_results:
|
for result in recent_results:
|
||||||
result['percent'] = round(100*result['result']['score']/result['result']['max'])
|
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)
|
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 request.method == 'POST':
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
new_user = User()
|
new_user = User()
|
||||||
new_user.set_username = request.form.get('username').lower()
|
new_user.set_username(request.form.get('username').lower())
|
||||||
new_user.set_email = request.form.get('email').lower()
|
new_user.set_email(request.form.get('email').lower())
|
||||||
new_user.set_password = request.form.get('password').lower()
|
new_user.set_password(request.form.get('password'))
|
||||||
success, message = new_user.register()
|
success, message = new_user.register()
|
||||||
if success:
|
if success:
|
||||||
flash(message=f'{message} Please log in to continue.', category='success')
|
flash(message=f'{message} Please log in to continue.', category='success')
|
||||||
@ -95,7 +95,7 @@ def _register():
|
|||||||
return jsonify({'error': message}), 401
|
return jsonify({'error': message}), 401
|
||||||
errors = [*form.username.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
|
errors = [*form.username.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
|
||||||
return jsonify({ 'error': errors}), 400
|
return jsonify({ 'error': errors}), 400
|
||||||
return render_template('admin/auth/register.html')
|
return render_template('admin/auth/register.html', form=form)
|
||||||
|
|
||||||
@admin.route('/reset/')
|
@admin.route('/reset/')
|
||||||
def _reset():
|
def _reset():
|
||||||
@ -149,9 +149,9 @@ def _users():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
password = request.form.get('password')
|
password = request.form.get('password')
|
||||||
new_user = User()
|
new_user = User()
|
||||||
new_user.set_username = request.form.get('username').lower()
|
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_password(secrets.token_hex(12)) if not password else password
|
||||||
new_user.set_email = request.form.get('email')
|
new_user.set_email(request.form.get('email'))
|
||||||
success, message = new_user.register(notify=request.form.get('notify'))
|
success, message = new_user.register(notify=request.form.get('notify'))
|
||||||
if success: return jsonify({'success': message}), 200
|
if success: return jsonify({'success': message}), 200
|
||||||
return jsonify({'error': message}), 401
|
return jsonify({'error': message}), 401
|
||||||
@ -192,23 +192,26 @@ def _update_user(id:str):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if not user: return jsonify({'error': 'User does not exist.'}), 400
|
if not user: return jsonify({'error': 'User does not exist.'}), 400
|
||||||
if form.validate_on_submit():
|
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(
|
success, message = user.update(
|
||||||
password = request.form.get('password'),
|
password = request.form.get('password'),
|
||||||
email = request.form.get('email'),
|
email = request.form.get('email'),
|
||||||
notify = request.form.get('notify')
|
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
|
return jsonify({'error': message}), 400
|
||||||
errors = [*form.confirm_password.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
|
errors = [*form.confirm_password.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
|
||||||
return jsonify({ 'error': errors}), 400
|
return jsonify({ 'error': errors}), 400
|
||||||
if not user:
|
if not user:
|
||||||
flash('User not found.', 'error')
|
flash('User not found.', 'error')
|
||||||
return redirect(url_for('admin._users'))
|
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'])
|
@admin.route('/settings/questions/', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def _quesitons():
|
def _questions():
|
||||||
form = UploadData()
|
form = UploadData()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@ -226,7 +229,7 @@ def _quesitons():
|
|||||||
return jsonify({ 'error': errors}), 400
|
return jsonify({ 'error': errors}), 400
|
||||||
|
|
||||||
data = Dataset.query.all()
|
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'])
|
@admin.route('/settings/questions/edit/', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@ -248,6 +251,7 @@ def _tests(filter:str=None):
|
|||||||
tests = None
|
tests = None
|
||||||
_tests = Test.query.all()
|
_tests = Test.query.all()
|
||||||
form = None
|
form = None
|
||||||
|
now = datetime.now()
|
||||||
if not datasets:
|
if not datasets:
|
||||||
flash('There are no available question datasets. Please upload a question dataset in order to set up an exam.', 'error')
|
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'))
|
return redirect(url_for('admin._questions'))
|
||||||
@ -255,21 +259,21 @@ def _tests(filter:str=None):
|
|||||||
if filter == 'create':
|
if filter == 'create':
|
||||||
form = CreateTest()
|
form = CreateTest()
|
||||||
form.time_limit.choices = get_time_options()
|
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.time_limit.default='none'
|
||||||
form.process()
|
form.process()
|
||||||
display_title = ''
|
display_title = ''
|
||||||
error_none = ''
|
error_none = ''
|
||||||
if filter in [None, '', 'active']:
|
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'
|
display_title = 'Active Exams'
|
||||||
error_none = 'There are no exams that are currently active. You can create one using the Creat Exam form.'
|
error_none = 'There are no exams that are currently active. You can create one using the Creat Exam form.'
|
||||||
if filter == 'expired':
|
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'
|
display_title = 'Expired Exams'
|
||||||
error_none = 'There are no expired exams. Exams will appear in this category after their expiration date has passed.'
|
error_none = 'There are no expired exams. Exams will appear in this category after their expiration date has passed.'
|
||||||
if filter == 'scheduled':
|
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'
|
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.'
|
error_none = 'There are no scheduled exams pending. You can schedule an exam for the future using the Create Exam form.'
|
||||||
if filter == 'all':
|
if filter == 'all':
|
||||||
@ -287,11 +291,12 @@ def _create_test():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
new_test = Test()
|
new_test = Test()
|
||||||
new_test.start_date = request.form.get('start_date')
|
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 = 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')
|
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()
|
success, message = new_test.create()
|
||||||
if success:
|
if success:
|
||||||
flash(message=message, category='success')
|
flash(message=message, category='success')
|
||||||
@ -371,7 +376,7 @@ def _view_entry(id:str=None):
|
|||||||
flash('Invalid entry ID.', 'error')
|
flash('Invalid entry ID.', 'error')
|
||||||
return redirect(url_for('admin._view_entries'))
|
return redirect(url_for('admin._view_entries'))
|
||||||
test = entry['test']
|
test = entry['test']
|
||||||
dataset = test['dataset']
|
dataset = test.dataset
|
||||||
dataset_path = dataset.get_file()
|
dataset_path = dataset.get_file()
|
||||||
with open(dataset_path, 'r') as _dataset:
|
with open(dataset_path, 'r') as _dataset:
|
||||||
data = loads(_dataset.read())
|
data = loads(_dataset.read())
|
||||||
|
@ -16,21 +16,21 @@ def _fetch_questions():
|
|||||||
id = request.get_json()['id']
|
id = request.get_json()['id']
|
||||||
entry = Entry.query.filter_by(id=id).first()
|
entry = Entry.query.filter_by(id=id).first()
|
||||||
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400
|
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400
|
||||||
test = entry['test']
|
test = entry.test
|
||||||
user_code = entry['user_code']
|
user_code = entry.user_code
|
||||||
time_limit = test['time_limit']
|
time_limit = test.time_limit
|
||||||
time_adjustment = 0
|
time_adjustment = 0
|
||||||
if time_limit:
|
if time_limit:
|
||||||
_time_limit = int(time_limit)
|
_time_limit = int(time_limit)
|
||||||
if user_code:
|
if user_code:
|
||||||
time_adjustment = test['time_adjustments'][user_code]
|
time_adjustment = test.time_adjustments[user_code]
|
||||||
_time_limit += time_adjustment
|
_time_limit += time_adjustment
|
||||||
end_delta = timedelta(minutes=_time_limit)
|
end_delta = timedelta(minutes=_time_limit)
|
||||||
end_time = datetime.utcnow() + end_delta
|
end_time = datetime.utcnow() + end_delta
|
||||||
else:
|
else:
|
||||||
end_time = None
|
end_time = None
|
||||||
entry.start()
|
entry.start()
|
||||||
dataset = test['dataset']
|
dataset = test.dataset
|
||||||
success, message = dataset.check_file()
|
success, message = dataset.check_file()
|
||||||
if not success: return jsonify({'error': message}), 500
|
if not success: return jsonify({'error': message}), 500
|
||||||
data_path = dataset.get_file()
|
data_path = dataset.get_file()
|
||||||
@ -51,7 +51,7 @@ def _submit_quiz():
|
|||||||
entry = Entry.query.filter_by(id=id).first()
|
entry = Entry.query.filter_by(id=id).first()
|
||||||
if not entry: return jsonify({'error': 'Unrecognised ID.'}), 400
|
if not entry: return jsonify({'error': 'Unrecognised ID.'}), 400
|
||||||
test = entry['test']
|
test = entry['test']
|
||||||
dataset = test['dataset']
|
dataset = test.dataset
|
||||||
success, message = dataset.check_file()
|
success, message = dataset.check_file()
|
||||||
if not success: return jsonify({'error': message}), 500
|
if not success: return jsonify({'error': message}), 500
|
||||||
data_path = dataset.get_file()
|
data_path = dataset.get_file()
|
||||||
|
@ -2,10 +2,11 @@ from ..tools.forms import value
|
|||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
||||||
from wtforms import BooleanField, DateField, IntegerField, PasswordField, SelectField, StringField
|
from wtforms import BooleanField, IntegerField, PasswordField, SelectField, StringField
|
||||||
|
from wtforms.fields import DateTimeLocalField
|
||||||
from wtforms.validators import InputRequired, Email, EqualTo, Length, Optional
|
from wtforms.validators import InputRequired, Email, EqualTo, Length, Optional
|
||||||
|
|
||||||
from datetime import date, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
|
|
||||||
class Login(FlaskForm):
|
class Login(FlaskForm):
|
||||||
username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)])
|
username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)])
|
||||||
@ -50,8 +51,8 @@ class UpdateAccount(FlaskForm):
|
|||||||
password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')])
|
password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')])
|
||||||
|
|
||||||
class CreateTest(FlaskForm):
|
class CreateTest(FlaskForm):
|
||||||
start_date = DateField('Start Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() )
|
start_date = DateTimeLocalField('Start Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = datetime.now() )
|
||||||
expiry_date = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
expiry_date = DateTimeLocalField('Expiry Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
||||||
time_limit = SelectField('Time Limit')
|
time_limit = SelectField('Time Limit')
|
||||||
dataset = SelectField('Question Dataset')
|
dataset = SelectField('Question Dataset')
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class Dataset(db.Model):
|
|||||||
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
generate_id.setter
|
generate_id.setter
|
||||||
def generate_id(self): self.id = uuid4.hex()
|
def generate_id(self): self.id = uuid4().hex
|
||||||
|
|
||||||
def make_default(self):
|
def make_default(self):
|
||||||
for dataset in Dataset.query.all():
|
for dataset in Dataset.query.all():
|
||||||
|
@ -32,7 +32,7 @@ class Entry(db.Model):
|
|||||||
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
generate_id.setter
|
generate_id.setter
|
||||||
def generate_id(self): self.id = uuid4.hex()
|
def generate_id(self): self.id = uuid4().hex
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def set_first_name(self): raise AttributeError('set_first_name is not a readable attribute.')
|
def set_first_name(self): raise AttributeError('set_first_name is not a readable attribute.')
|
||||||
|
@ -6,8 +6,6 @@ from ..tools.logs import write
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from json import dump, loads
|
|
||||||
import os
|
|
||||||
import secrets
|
import secrets
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@ -24,13 +22,13 @@ class Test(db.Model):
|
|||||||
entries = db.relationship('Entry', backref='test')
|
entries = db.relationship('Entry', backref='test')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<test with code {self.code} was created by {current_user.get_username()}.>'
|
return f'<Test with code {self.get_code()} was created by {current_user.get_username()}.>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
generate_id.setter
|
generate_id.setter
|
||||||
def generate_id(self): self.id = uuid4.hex()
|
def generate_id(self): self.id = uuid4().hex
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def generate_code(self): raise AttributeError('generate_code is not a readable attribute.')
|
def generate_code(self): raise AttributeError('generate_code is not a readable attribute.')
|
||||||
@ -65,26 +63,26 @@ class Test(db.Model):
|
|||||||
if self.entries: return False, f'Cannot delete a test with submitted entries.'
|
if self.entries: return False, f'Cannot delete a test with submitted entries.'
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
write('system.log', f'Test with code {code} has been deleted by {current_user.get_username()}.')
|
write('system.log', f'Test with code {self.get_code()} has been deleted by {current_user.get_username()}.')
|
||||||
return True, f'Test with code {code} has been deleted.'
|
return True, f'Test with code {self.get_code()} has been deleted.'
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if self.start_date.date() > now.date():
|
if self.start_date.date() > now.date():
|
||||||
self.start_date = now
|
self.start_date = now
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
write('system.log', f'Test with code {self.code} has been started by {current_user.get_username()}.')
|
write('system.log', f'Test with code {self.get_code()} has been started by {current_user.get_username()}.')
|
||||||
return True, f'Test with code {self.code} has been started.'
|
return True, f'Test with code {self.get_code()} has been started.'
|
||||||
return False, f'Test with code {self.code} has already started.'
|
return False, f'Test with code {self.get_code()} has already started.'
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if self.end_date.date() > now.date():
|
if self.end_date >= now:
|
||||||
self.end_date = now
|
self.end_date = now
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
write('system.log', f'Test with code {self.code} ended by {current_user.get_username()}.')
|
write('system.log', f'Test with code {self.get_code()} ended by {current_user.get_username()}.')
|
||||||
return True, f'Test with code {self.code} has been ended.'
|
return True, f'Test with code {self.get_code()} has been ended.'
|
||||||
return False, f'Test with code {self.code} has already ended.'
|
return False, f'Test with code {self.get_code()} has already ended.'
|
||||||
|
|
||||||
def add_adjustment(self, time:int):
|
def add_adjustment(self, time:int):
|
||||||
adjustments = self.adjustments if self.adjustments is not None else {}
|
adjustments = self.adjustments if self.adjustments is not None else {}
|
||||||
|
@ -26,7 +26,7 @@ class User(UserMixin, db.Model):
|
|||||||
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
generate_id.setter
|
generate_id.setter
|
||||||
def generate_id(self): self.id = uuid4.hex()
|
def generate_id(self): self.id = uuid4().hex
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def set_username(self): raise AttributeError('set_username is not a readable attribute.')
|
def set_username(self): raise AttributeError('set_username is not a readable attribute.')
|
||||||
@ -83,7 +83,7 @@ class User(UserMixin, db.Model):
|
|||||||
print('Password', new_password)
|
print('Password', new_password)
|
||||||
print('Reset Token', self.reset_token)
|
print('Reset Token', self.reset_token)
|
||||||
print('Verification Token', self.verification_token)
|
print('Verification Token', self.verification_token)
|
||||||
print('Reset Link', f'{url_for("auth._reset", token=self.reset_token, verification=self.verification_token, _external=True)}')
|
print('Reset Link', f'{url_for("admin._reset", token=self.reset_token, verification=self.verification_token, _external=True)}')
|
||||||
return jsonify({'success': 'Your password reset link has been generated.'}), 200
|
return jsonify({'success': 'Your password reset link has been generated.'}), 200
|
||||||
|
|
||||||
def clear_reset_tokens(self):
|
def clear_reset_tokens(self):
|
||||||
@ -103,6 +103,5 @@ class User(UserMixin, db.Model):
|
|||||||
if password: self.set_password(password)
|
if password: self.set_password(password)
|
||||||
if email: self.set_email(email)
|
if email: self.set_email(email)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.'
|
write('system.log', f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.')
|
||||||
write('system.log', message)
|
return True, f'Account {self.get_username()} has been updated.'
|
||||||
return True, message
|
|
||||||
|
@ -143,7 +143,7 @@ $("#btn-start-quiz").click(function(event){
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/api/questions/`,
|
url: `/api/questions/`,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({'_id': _id}),
|
data: JSON.stringify({'id': id}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
$(this).fadeOut();
|
$(this).fadeOut();
|
||||||
@ -223,7 +223,7 @@ $("#q-review-answers").click(function(event){
|
|||||||
|
|
||||||
$(".quiz-button-submit").click(function(event){
|
$(".quiz-button-submit").click(function(event){
|
||||||
let submission = {
|
let submission = {
|
||||||
'_id': _id,
|
'id': id,
|
||||||
'answers': answers
|
'answers': answers
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,7 +607,7 @@ function count_questions(status) {
|
|||||||
|
|
||||||
// Variable Definitions
|
// Variable Definitions
|
||||||
|
|
||||||
const _id = window.localStorage.getItem('_id');
|
const id = window.localStorage.getItem('id');
|
||||||
|
|
||||||
var current_question = 0;
|
var current_question = 0;
|
||||||
var total_questions = 0;
|
var total_questions = 0;
|
||||||
|
@ -23,8 +23,8 @@ $('form[name=form-quiz-start]').submit(function(event) {
|
|||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
var _id = response._id
|
var id = response.id
|
||||||
window.localStorage.setItem('_id', _id);
|
window.localStorage.setItem('id', id);
|
||||||
window.location.href = `/test/`;
|
window.location.href = `/test/`;
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
|
@ -2,7 +2,7 @@ from .data import load
|
|||||||
from ..models import User
|
from ..models import User
|
||||||
|
|
||||||
from flask import abort, redirect
|
from flask import abort, redirect
|
||||||
from flask.helpers import url_for
|
from flask.helpers import flash, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@ -10,7 +10,9 @@ from functools import wraps
|
|||||||
def require_account_creation(function):
|
def require_account_creation(function):
|
||||||
@wraps(function)
|
@wraps(function)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if User.query.count() == 0: return redirect(url_for('views._register'))
|
if User.query.count() == 0:
|
||||||
|
flash('Please register a user account.', 'alert')
|
||||||
|
return redirect(url_for('admin._register'))
|
||||||
return function(*args, **kwargs)
|
return function(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ def check_is_json(file):
|
|||||||
def validate_json(file):
|
def validate_json(file):
|
||||||
file.stream.seek(0)
|
file.stream.seek(0)
|
||||||
data = json.loads(file.read())
|
data = json.loads(file.read())
|
||||||
if not type(data) is list: return False
|
if not isinstance(data, list): return False
|
||||||
|
return True
|
||||||
|
|
||||||
def randomise_list(list:list):
|
def randomise_list(list:list):
|
||||||
_list = list.copy()
|
_list = list.copy()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
from ..models import Dataset
|
|
||||||
from ..modules import db
|
from ..modules import db
|
||||||
|
|
||||||
from wtforms.validators import ValidationError
|
from wtforms.validators import ValidationError
|
||||||
@ -46,11 +45,12 @@ def get_time_options():
|
|||||||
return time_options
|
return time_options
|
||||||
|
|
||||||
def get_dataset_choices():
|
def get_dataset_choices():
|
||||||
|
from ..models import Dataset
|
||||||
datasets = Dataset.query.all()
|
datasets = Dataset.query.all()
|
||||||
dataset_choices = []
|
dataset_choices = []
|
||||||
for dataset in datasets:
|
for dataset in datasets:
|
||||||
label = dataset['date'].strftime('%Y%m%d%H%M%S')
|
label = dataset.date.strftime('%Y%m%d%H%M%S')
|
||||||
label = f'{label} (Default)' if dataset.default else label
|
label = f'{label} (Default)' if dataset.default else label
|
||||||
choice = (dataset['id'], label)
|
choice = (dataset.id, label)
|
||||||
dataset_choices.append(choice)
|
dataset_choices.append(choice)
|
||||||
return dataset_choices
|
return dataset_choices
|
@ -1,4 +1,4 @@
|
|||||||
from ..config import Config
|
from .config import Config
|
||||||
|
|
||||||
from flask import Blueprint, redirect, request, render_template
|
from flask import Blueprint, redirect, request, render_template
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from app.models import User
|
||||||
from app.modules import bootstrap, csrf, db, login_manager, mail
|
from app.modules import bootstrap, csrf, db, login_manager, mail
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
@ -21,8 +22,8 @@ def create_app():
|
|||||||
|
|
||||||
login_manager.login_view = 'admin._login'
|
login_manager.login_view = 'admin._login'
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def _load_user(user_id):
|
def _load_user(id):
|
||||||
pass
|
return User.query.filter_by(id=id).first()
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def _404_handler(error):
|
def _404_handler(error):
|
||||||
@ -32,7 +33,7 @@ def create_app():
|
|||||||
return jsonify({'error':'Could not validate a secure connection.'}), 403
|
return jsonify({'error':'Could not validate a secure connection.'}), 403
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def _now():
|
def _now():
|
||||||
return {'now': datetime.utcnow()}
|
return {'now': datetime.now()}
|
||||||
|
|
||||||
from app.admin.views import admin
|
from app.admin.views import admin
|
||||||
from app.api.views import api
|
from app.api.views import api
|
||||||
|
Loading…
Reference in New Issue
Block a user