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
|
||||
$('.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 ‘{{ user.username }}’?</h2>
|
||||
<h2 class="form-heading">Delete User ‘{{ user.get_username() }}’?</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 ‘{{ user.username }}’</h2>
|
||||
<h2 class="form-heading">Update User ‘{{ user.get_username() }}’</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())
|
||||
|
@ -16,21 +16,21 @@ def _fetch_questions():
|
||||
id = request.get_json()['id']
|
||||
entry = Entry.query.filter_by(id=id).first()
|
||||
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400
|
||||
test = entry['test']
|
||||
user_code = entry['user_code']
|
||||
time_limit = test['time_limit']
|
||||
test = entry.test
|
||||
user_code = entry.user_code
|
||||
time_limit = test.time_limit
|
||||
time_adjustment = 0
|
||||
if time_limit:
|
||||
_time_limit = int(time_limit)
|
||||
if user_code:
|
||||
time_adjustment = test['time_adjustments'][user_code]
|
||||
time_adjustment = test.time_adjustments[user_code]
|
||||
_time_limit += time_adjustment
|
||||
end_delta = timedelta(minutes=_time_limit)
|
||||
end_time = datetime.utcnow() + end_delta
|
||||
else:
|
||||
end_time = None
|
||||
entry.start()
|
||||
dataset = test['dataset']
|
||||
dataset = test.dataset
|
||||
success, message = dataset.check_file()
|
||||
if not success: return jsonify({'error': message}), 500
|
||||
data_path = dataset.get_file()
|
||||
@ -51,7 +51,7 @@ def _submit_quiz():
|
||||
entry = Entry.query.filter_by(id=id).first()
|
||||
if not entry: return jsonify({'error': 'Unrecognised ID.'}), 400
|
||||
test = entry['test']
|
||||
dataset = test['dataset']
|
||||
dataset = test.dataset
|
||||
success, message = dataset.check_file()
|
||||
if not success: return jsonify({'error': message}), 500
|
||||
data_path = dataset.get_file()
|
||||
|
@ -2,10 +2,11 @@ from ..tools.forms import value
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
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 datetime import date, timedelta
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
class Login(FlaskForm):
|
||||
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.')])
|
||||
|
||||
class CreateTest(FlaskForm):
|
||||
start_date = DateField('Start Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() )
|
||||
expiry_date = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
||||
start_date = DateTimeLocalField('Start Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = datetime.now() )
|
||||
expiry_date = DateTimeLocalField('Expiry Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
||||
time_limit = SelectField('Time Limit')
|
||||
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.')
|
||||
|
||||
generate_id.setter
|
||||
def generate_id(self): self.id = uuid4.hex()
|
||||
def generate_id(self): self.id = uuid4().hex
|
||||
|
||||
def make_default(self):
|
||||
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.')
|
||||
|
||||
generate_id.setter
|
||||
def generate_id(self): self.id = uuid4.hex()
|
||||
def generate_id(self): self.id = uuid4().hex
|
||||
|
||||
@property
|
||||
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 datetime import date, datetime
|
||||
from json import dump, loads
|
||||
import os
|
||||
import secrets
|
||||
from uuid import uuid4
|
||||
|
||||
@ -24,13 +22,13 @@ class Test(db.Model):
|
||||
entries = db.relationship('Entry', backref='test')
|
||||
|
||||
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
|
||||
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||
|
||||
generate_id.setter
|
||||
def generate_id(self): self.id = uuid4.hex()
|
||||
def generate_id(self): self.id = uuid4().hex
|
||||
|
||||
@property
|
||||
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.'
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
write('system.log', f'Test with code {code} has been deleted by {current_user.get_username()}.')
|
||||
return True, f'Test with code {code} has been deleted.'
|
||||
write('system.log', f'Test with code {self.get_code()} has been deleted by {current_user.get_username()}.')
|
||||
return True, f'Test with code {self.get_code()} has been deleted.'
|
||||
|
||||
def start(self):
|
||||
now = datetime.now()
|
||||
if self.start_date.date() > now.date():
|
||||
self.start_date = now
|
||||
db.session.commit()
|
||||
write('system.log', f'Test with code {self.code} has been started by {current_user.get_username()}.')
|
||||
return True, f'Test with code {self.code} has been started.'
|
||||
return False, f'Test with code {self.code} has already started.'
|
||||
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.get_code()} has been started.'
|
||||
return False, f'Test with code {self.get_code()} has already started.'
|
||||
|
||||
def end(self):
|
||||
now = datetime.now()
|
||||
if self.end_date.date() > now.date():
|
||||
if self.end_date >= now:
|
||||
self.end_date = now
|
||||
db.session.commit()
|
||||
write('system.log', f'Test with code {self.code} ended by {current_user.get_username()}.')
|
||||
return True, f'Test with code {self.code} has been ended.'
|
||||
return False, f'Test with code {self.code} has already ended.'
|
||||
write('system.log', f'Test with code {self.get_code()} ended by {current_user.get_username()}.')
|
||||
return True, f'Test with code {self.get_code()} has been ended.'
|
||||
return False, f'Test with code {self.get_code()} has already ended.'
|
||||
|
||||
def add_adjustment(self, time:int):
|
||||
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.')
|
||||
|
||||
generate_id.setter
|
||||
def generate_id(self): self.id = uuid4.hex()
|
||||
def generate_id(self): self.id = uuid4().hex
|
||||
|
||||
@property
|
||||
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('Reset Token', self.reset_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
|
||||
|
||||
def clear_reset_tokens(self):
|
||||
@ -103,6 +103,5 @@ class User(UserMixin, db.Model):
|
||||
if password: self.set_password(password)
|
||||
if email: self.set_email(email)
|
||||
db.session.commit()
|
||||
message = f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.'
|
||||
write('system.log', message)
|
||||
return True, message
|
||||
write('system.log', f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.')
|
||||
return True, f'Account {self.get_username()} has been updated.'
|
||||
|
@ -143,7 +143,7 @@ $("#btn-start-quiz").click(function(event){
|
||||
$.ajax({
|
||||
url: `/api/questions/`,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({'_id': _id}),
|
||||
data: JSON.stringify({'id': id}),
|
||||
contentType: "application/json",
|
||||
success: function(response) {
|
||||
$(this).fadeOut();
|
||||
@ -223,7 +223,7 @@ $("#q-review-answers").click(function(event){
|
||||
|
||||
$(".quiz-button-submit").click(function(event){
|
||||
let submission = {
|
||||
'_id': _id,
|
||||
'id': id,
|
||||
'answers': answers
|
||||
}
|
||||
|
||||
@ -607,7 +607,7 @@ function count_questions(status) {
|
||||
|
||||
// Variable Definitions
|
||||
|
||||
const _id = window.localStorage.getItem('_id');
|
||||
const id = window.localStorage.getItem('id');
|
||||
|
||||
var current_question = 0;
|
||||
var total_questions = 0;
|
||||
|
@ -23,8 +23,8 @@ $('form[name=form-quiz-start]').submit(function(event) {
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
var _id = response._id
|
||||
window.localStorage.setItem('_id', _id);
|
||||
var id = response.id
|
||||
window.localStorage.setItem('id', id);
|
||||
window.location.href = `/test/`;
|
||||
},
|
||||
error: function(response) {
|
||||
|
@ -2,7 +2,7 @@ from .data import load
|
||||
from ..models import User
|
||||
|
||||
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 functools import wraps
|
||||
@ -10,7 +10,9 @@ from functools import wraps
|
||||
def require_account_creation(function):
|
||||
@wraps(function)
|
||||
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 wrapper
|
||||
|
||||
|
@ -18,7 +18,8 @@ def check_is_json(file):
|
||||
def validate_json(file):
|
||||
file.stream.seek(0)
|
||||
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):
|
||||
_list = list.copy()
|
||||
|
@ -1,5 +1,4 @@
|
||||
|
||||
from ..models import Dataset
|
||||
from ..modules import db
|
||||
|
||||
from wtforms.validators import ValidationError
|
||||
@ -46,11 +45,12 @@ def get_time_options():
|
||||
return time_options
|
||||
|
||||
def get_dataset_choices():
|
||||
from ..models import Dataset
|
||||
datasets = Dataset.query.all()
|
||||
dataset_choices = []
|
||||
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
|
||||
choice = (dataset['id'], label)
|
||||
choice = (dataset.id, label)
|
||||
dataset_choices.append(choice)
|
||||
return dataset_choices
|
@ -1,4 +1,4 @@
|
||||
from ..config import Config
|
||||
from .config import Config
|
||||
|
||||
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 config import Config
|
||||
|
||||
@ -21,8 +22,8 @@ def create_app():
|
||||
|
||||
login_manager.login_view = 'admin._login'
|
||||
@login_manager.user_loader
|
||||
def _load_user(user_id):
|
||||
pass
|
||||
def _load_user(id):
|
||||
return User.query.filter_by(id=id).first()
|
||||
|
||||
@app.errorhandler(404)
|
||||
def _404_handler(error):
|
||||
@ -32,7 +33,7 @@ def create_app():
|
||||
return jsonify({'error':'Could not validate a secure connection.'}), 403
|
||||
@app.context_processor
|
||||
def _now():
|
||||
return {'now': datetime.utcnow()}
|
||||
return {'now': datetime.now()}
|
||||
|
||||
from app.admin.views import admin
|
||||
from app.api.views import api
|
||||
|
Loading…
Reference in New Issue
Block a user