Finished delete and data table fiew for tests
This commit is contained in:
parent
c0d79d1bc7
commit
2d0bb883bd
@ -1,6 +1,6 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, PasswordField, BooleanField, DateField, SelectField
|
from wtforms import StringField, PasswordField, BooleanField, DateField, SelectField
|
||||||
from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional, ValidationError
|
from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
class LoginForm(FlaskForm):
|
||||||
@ -45,11 +45,12 @@ class UpdateAccountForm(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() )
|
||||||
time_options = [
|
time_options = [
|
||||||
('none', 'None'),
|
('none', 'None'),
|
||||||
('60', '1 hour'),
|
('60', '1 hour'),
|
||||||
('90', '1 hour 30 minutes'),
|
('90', '1 hour 30 minutes'),
|
||||||
('120', '2 hours')
|
('120', '2 hours')
|
||||||
]
|
]
|
||||||
expiry = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
expiry_date = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
||||||
time_limit = SelectField('Time Limit', choices=time_options)
|
time_limit = SelectField('Time Limit', choices=time_options)
|
@ -2,22 +2,25 @@ import secrets
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from flask import flash, jsonify
|
from flask import flash, jsonify
|
||||||
|
import secrets
|
||||||
|
|
||||||
from main import db
|
from main import db
|
||||||
from security import encrypt, decrypt
|
from security import encrypt
|
||||||
|
|
||||||
class Test:
|
class Test:
|
||||||
def __init__(self, _id=None, expiry=None, time_limit=None, creator=None):
|
def __init__(self, _id=None, start_date=None, expiry_date=None, time_limit=None, creator=None):
|
||||||
self._id = _id
|
self._id = _id
|
||||||
self.expiry = expiry
|
self.start_date = start_date
|
||||||
|
self.expiry_date = expiry_date
|
||||||
self.time_limit = None if time_limit == 'none' or time_limit == '' else time_limit
|
self.time_limit = None if time_limit == 'none' or time_limit == '' else time_limit
|
||||||
self.creator = creator
|
self.creator = creator
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
test = {
|
test = {
|
||||||
'_id': self._id,
|
'_id': self._id,
|
||||||
'date': datetime.today(),
|
'date_created': datetime.today(),
|
||||||
'expiry': self.expiry,
|
'start_date': self.start_date,
|
||||||
|
'expiry_date': self.expiry_date,
|
||||||
'time_limit': self.time_limit,
|
'time_limit': self.time_limit,
|
||||||
'creator': encrypt(self.creator),
|
'creator': encrypt(self.creator),
|
||||||
'test_code': secrets.token_hex(6).upper()
|
'test_code': secrets.token_hex(6).upper()
|
||||||
@ -27,19 +30,66 @@ class Test:
|
|||||||
return jsonify({'success': test}), 200
|
return jsonify({'success': test}), 200
|
||||||
return jsonify({'error': f'Could not create exam. An error occurred.'}), 400
|
return jsonify({'error': f'Could not create exam. An error occurred.'}), 400
|
||||||
|
|
||||||
def add_user_code(self, user_code, time_adjustment):
|
def add_time_adjustment(self, time_adjustment):
|
||||||
code = {
|
code = {
|
||||||
'_id': uuid4().hex,
|
'_id': uuid4().hex,
|
||||||
'user_code': user_code,
|
'user_code': secrets.token_hex(2).upper(),
|
||||||
'time_adjustment': time_adjustment
|
'time_adjustment': time_adjustment
|
||||||
}
|
}
|
||||||
if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': code}},upsert=False):
|
if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': code}},upsert=False):
|
||||||
return jsonify({'success': code})
|
return jsonify({'success': code})
|
||||||
else:
|
return jsonify({'error': 'Failed to add the time adjustment. An error occurred.'}), 400
|
||||||
return jsonify({'error': 'An error occurred.'}), 400
|
|
||||||
|
def remove_time_adjustment(self, _id):
|
||||||
|
if db.tests.find_one_and_update({'_id': self._id}, {'$pull': {'time_adjustments': {'_id': _id} }}):
|
||||||
|
message = 'Time adjustment has been deleted.'
|
||||||
|
flash(message, 'success')
|
||||||
|
return jsonify({'success': message})
|
||||||
|
return jsonify({'error': 'Failed to delete the time adjustment. An error occurred.'}), 400
|
||||||
|
|
||||||
def render_test_code(self, test_code):
|
def render_test_code(self, test_code):
|
||||||
return '—'.join([test_code[:4], test_code[4:8], test_code[8:]])
|
return '—'.join([test_code[:4], test_code[4:8], test_code[8:]])
|
||||||
|
|
||||||
def parse_test_code(self, test_code):
|
def parse_test_code(self, test_code):
|
||||||
return test_code.replace('—', '')
|
return test_code.replace('—', '')
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
if db.tests.delete_one({'_id': self._id}):
|
||||||
|
message = 'Deleted exam.'
|
||||||
|
flash(message, 'alert')
|
||||||
|
return jsonify({'success': message}), 200
|
||||||
|
return jsonify({'error': f'Could not create exam. An error occurred.'}), 400
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
test = {}
|
||||||
|
updated = []
|
||||||
|
if not self.start_date == '' and self.start_date is not None:
|
||||||
|
test['start_date'] = self.start_date
|
||||||
|
updated.append('start date')
|
||||||
|
if not self.expiry_date == '' and self.expiry_date is not None:
|
||||||
|
test['expiry_date'] = self.expiry_date
|
||||||
|
updated.append('expiry date')
|
||||||
|
if not self.time_limit == '' and self.time_limit is not None:
|
||||||
|
test['time_limit'] = self.time_limit
|
||||||
|
updated.append('time limit')
|
||||||
|
output = ''
|
||||||
|
if len(updated) == 0:
|
||||||
|
flash(f'There were no changes requested for your account.', 'alert'), 200
|
||||||
|
return jsonify({'success': 'There were no changes requested for your account.'}), 200
|
||||||
|
elif len(updated) == 1:
|
||||||
|
output = updated[0]
|
||||||
|
elif len(updated) == 2:
|
||||||
|
output = ' and '.join(updated)
|
||||||
|
elif len(updated) > 2:
|
||||||
|
output = updated[0]
|
||||||
|
for index in range(1,len(updated)):
|
||||||
|
if index < len(updated) - 2:
|
||||||
|
output = ', '.join([output, updated[index]])
|
||||||
|
elif index == len(updated) - 2:
|
||||||
|
output = ', and '.join([output, updated[index]])
|
||||||
|
else:
|
||||||
|
output = ''.join([output, updated[index]])
|
||||||
|
db.tests.find_one_and_update({'_id': self._id}, {'$set': test})
|
||||||
|
_output = f'The {output} of the test {"has" if len(updated) == 1 else "have"} been updated.'
|
||||||
|
flash(_output)
|
||||||
|
return jsonify({'success': _output}), 200
|
@ -138,6 +138,19 @@ table.dataTable {
|
|||||||
|
|
||||||
.user-row-actions {
|
.user-row-actions {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-row-actions {
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataTables_wrapper .dt-buttons {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
float:none;
|
||||||
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-row-actions button {
|
.user-row-actions button {
|
||||||
@ -197,6 +210,10 @@ table.dataTable {
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fallback for Edge
|
/* Fallback for Edge
|
||||||
-------------------------------------------------- */
|
-------------------------------------------------- */
|
||||||
@supports (-ms-ime-align: auto) {
|
@supports (-ms-ime-align: auto) {
|
||||||
|
@ -350,12 +350,49 @@ $('form[name=form-update-account]').submit(function(event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.delete-test').click(function(event) {
|
||||||
|
|
||||||
|
_id = $(this).data('_id')
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `/admin/tests/delete/${_id}`,
|
||||||
|
type: 'GET',
|
||||||
|
success: function(response) {
|
||||||
|
window.location.href = '/admin/tests/';
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
|
alert.innerHTML = alert.innerHTML + `
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
|
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
|
alert.innerHTML = alert.innerHTML + `
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error[i]}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit and Delete Test Button Handlers
|
||||||
|
|
||||||
$('form[name=form-create-test]').submit(function(event) {
|
$('form[name=form-create-test]').submit(function(event) {
|
||||||
|
|
||||||
var $form = $(this);
|
var $form = $(this);
|
||||||
var alert = document.getElementById('alert-box');
|
var alert = document.getElementById('alert-box');
|
||||||
var data = $form.serialize();
|
var data = $form.serialize();
|
||||||
console.log(data)
|
|
||||||
alert.innerHTML = ''
|
alert.innerHTML = ''
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -364,10 +401,9 @@ $('form[name=form-create-test]').submit(function(event) {
|
|||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.reload();
|
window.location.href = '/admin/tests/';
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
console.log(response.responseJSON)
|
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
alert.innerHTML = alert.innerHTML + `
|
alert.innerHTML = alert.innerHTML + `
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
@ -396,8 +432,6 @@ $('form[name=form-create-test]').submit(function(event) {
|
|||||||
// Dismiss Cookie Alert
|
// Dismiss Cookie Alert
|
||||||
$('#dismiss-cookie-alert').click(function(event){
|
$('#dismiss-cookie-alert').click(function(event){
|
||||||
|
|
||||||
console.log('Foo')
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/cookies/',
|
url: '/cookies/',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
<div class="navbar navbar-expand-sm navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<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>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='scheduled') }}">Scheduled</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='expired') }}">Expired</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='all') }}">All</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin_views.tests', filter='create') }}">Create</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -2,7 +2,6 @@
|
|||||||
{% block title %} SKA Referee Test | Manage Users {% endblock %}
|
{% block title %} SKA Referee Test | Manage Users {% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Manage Users</h1>
|
<h1>Manage Users</h1>
|
||||||
|
|
||||||
<table id="user-table" class="table table-striped" style="width:100%">
|
<table id="user-table" class="table table-striped" style="width:100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -50,9 +49,7 @@
|
|||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
title="Update User"
|
title="Update User"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-lines-fill" viewBox="0 0 16 16">
|
<i class="bi bi-person-lines-fill button-icon"></i>
|
||||||
<path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="
|
href="
|
||||||
@ -66,16 +63,13 @@
|
|||||||
title="Delete User"
|
title="Delete User"
|
||||||
{% if user._id == get_id_from_cookie() %} onclick="return false" {% endif %}
|
{% if user._id == get_id_from_cookie() %} onclick="return false" {% endif %}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-x-fill" viewBox="0 0 16 16">
|
<i class="bi bi-person-x-fill button-icon"></i>
|
||||||
<path fill-rule="evenodd" d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm6.146-2.854a.5.5 0 0 1 .708 0L14 6.293l1.146-1.147a.5.5 0 0 1 .708.708L14.707 7l1.147 1.146a.5.5 0 0 1-.708.708L14 7.707l-1.146 1.147a.5.5 0 0 1-.708-.708L13.293 7l-1.147-1.146a.5.5 0 0 1 0-.708z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<form name="form-create-user" class="form-signin">
|
<form name="form-create-user" class="form-signin">
|
||||||
<h2 class="form-signin-heading">Create User</h2>
|
<h2 class="form-signin-heading">Create User</h2>
|
||||||
@ -100,10 +94,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<button title="Create User" class="btn btn-md btn-success btn-block" type="submit">
|
<button title="Create User" class="btn btn-md btn-success btn-block" type="submit">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 20 20">
|
<i class="bi bi-person-plus-fill button-icon"></i>
|
||||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
Create User
|
Create User
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
{% block title %} SKA Referee Test | Manage Exams {% endblock %}
|
{% block title %} SKA Referee Test | Manage Exams {% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Manage Exams</h1>
|
<h1>Manage Exams</h1>
|
||||||
{% if active_tests %}
|
{% include "admin/components/secondary-navs/tests.html" %}
|
||||||
|
<h2>{{ display_title }}</h2>
|
||||||
|
{% if tests %}
|
||||||
<table id="active-test-table" class="table table-striped" style="width:100%">
|
<table id="active-test-table" class="table table-striped" style="width:100%">
|
||||||
<thead>
|
<thead>
|
||||||
<caption>
|
|
||||||
Active Tests
|
|
||||||
</caption>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th data-priority="1">
|
<th data-priority="1">
|
||||||
Start Date
|
Start Date
|
||||||
@ -22,7 +21,7 @@
|
|||||||
Time Limit
|
Time Limit
|
||||||
</th>
|
</th>
|
||||||
<th data-priority="4">
|
<th data-priority="4">
|
||||||
Results
|
Entries
|
||||||
</th>
|
</th>
|
||||||
<th data-priority="1">
|
<th data-priority="1">
|
||||||
Actions
|
Actions
|
||||||
@ -30,16 +29,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for test in active_tests %}
|
{% for test in tests %}
|
||||||
<tr class="user-table-row">
|
<tr class="user-table-row">
|
||||||
<td>
|
<td>
|
||||||
{{ test.date.strftime('%d %b %Y') }}
|
{{ test.start_date.strftime('%d %b %Y') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
|
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ test.expiry }}
|
{{ test.expiry_date.strftime('%d %b %Y') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if test.time_limit == None -%}
|
{% if test.time_limit == None -%}
|
||||||
@ -55,61 +54,37 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ test.creator }}
|
{{ test.attempts|length }}
|
||||||
</td>
|
</td>
|
||||||
<td class="test-row-actions">
|
<td class="test-row-actions">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary edit-test"
|
||||||
|
data-_id="{{test._id}}"
|
||||||
title="Edit Exam"
|
title="Edit Exam"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-lines-fill" viewBox="0 0 16 16">
|
<i class="bi bi-file-earmark-text-fill button-icon"></i>
|
||||||
<path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger delete-test"
|
||||||
|
data-_id="{{test._id}}"
|
||||||
title="Delete Exam"
|
title="Delete Exam"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-x-fill" viewBox="0 0 16 16">
|
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
||||||
<path fill-rule="evenodd" d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm6.146-2.854a.5.5 0 0 1 .708 0L14 6.293l1.146-1.147a.5.5 0 0 1 .708.708L14.707 7l1.147 1.146a.5.5 0 0 1-.708.708L14 7.707l-1.146 1.147a.5.5 0 0 1-.708-.708L13.293 7l-1.147-1.146a.5.5 0 0 1 0-.708z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% elif not filter == 'create' %}
|
||||||
<div class="alert alert-primary alert-db-empty">
|
<div class="alert alert-primary alert-db-empty">
|
||||||
<i class="bi bi-info-circle-fill" aria-title="Alert" title="Alert"></i>
|
<i class="bi bi-info-circle-fill" aria-title="Alert" title="Alert"></i>
|
||||||
There are no active exams. Use the form below to create an exam.
|
{{ error_none }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if form %}
|
||||||
{% block custom_data_script %}
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('#active-test-table').DataTable({
|
|
||||||
'columnDefs': [
|
|
||||||
{'sortable': false, 'targets': [1,3,5]},
|
|
||||||
{'searchable': false, 'targets': [3,5]}
|
|
||||||
],
|
|
||||||
'order': [[0, 'desc'], [2, 'asc']],
|
|
||||||
'buttons': [
|
|
||||||
'copy', 'excel', 'pdf'
|
|
||||||
],
|
|
||||||
'responsive': 'true',
|
|
||||||
'colReorder': 'true',
|
|
||||||
'fixedHeader': 'true'
|
|
||||||
});
|
|
||||||
} );
|
|
||||||
$('#test-table').show();
|
|
||||||
$(window).trigger('resize');
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<form name="form-create-test" class="form-signin">
|
<form name="form-create-test" class="form-signin">
|
||||||
<h2 class="form-signin-heading">Create Exam</h2>
|
<h2 class="form-signin-heading">Create Exam</h2>
|
||||||
@ -131,10 +106,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<button title="Create Exam" class="btn btn-md btn-success btn-block" type="submit">
|
<button title="Create Exam" class="btn btn-md btn-success btn-block" type="submit">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 20 20">
|
<i class="bi bi-file-earmark-plus-fill button-icon"></i>
|
||||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
||||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
|
||||||
</svg>
|
|
||||||
Create Exam
|
Create Exam
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -142,4 +114,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% if tests %}
|
||||||
|
{% block custom_data_script %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#active-test-table').DataTable({
|
||||||
|
'columnDefs': [
|
||||||
|
{'sortable': false, 'targets': [1,3,5]},
|
||||||
|
{'searchable': false, 'targets': [3,5]}
|
||||||
|
],
|
||||||
|
'order': [[0, 'desc'], [2, 'asc']],
|
||||||
|
dom: 'lfBrtip',
|
||||||
|
'buttons': [
|
||||||
|
{
|
||||||
|
extend: 'print',
|
||||||
|
exportOptions: {
|
||||||
|
columns: [0, 1, 2, 3]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'excel',
|
||||||
|
exportOptions: {
|
||||||
|
columns: [0, 1, 2, 3]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'pdf',
|
||||||
|
exportOptions: {
|
||||||
|
columns: [0, 1, 2, 3]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'responsive': 'true',
|
||||||
|
'colReorder': 'true',
|
||||||
|
'fixedHeader': 'true',
|
||||||
|
});
|
||||||
|
// $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') -->
|
||||||
|
} );
|
||||||
|
$('#test-table').show();
|
||||||
|
$(window).trigger('resize');
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% endif %}
|
@ -243,27 +243,68 @@ def questions():
|
|||||||
def upload_questions():
|
def upload_questions():
|
||||||
return render_template('/admin/settings/upload-questions.html')
|
return render_template('/admin/settings/upload-questions.html')
|
||||||
|
|
||||||
@views.route('/tests/', methods=['GET','POST'])
|
@views.route('/tests/<filter>/', methods=['GET'])
|
||||||
|
@views.route('/tests/', methods=['GET'])
|
||||||
@admin_account_required
|
@admin_account_required
|
||||||
@login_required
|
@login_required
|
||||||
def tests():
|
def tests(filter=''):
|
||||||
|
if filter not in ['', 'create', 'active', 'scheduled', 'expired', 'all']:
|
||||||
|
return abort(404)
|
||||||
|
if filter == 'create':
|
||||||
|
from .forms import CreateTest
|
||||||
|
form = CreateTest()
|
||||||
|
form.time_limit.default='none'
|
||||||
|
form.process()
|
||||||
|
display_title = ''
|
||||||
|
error_none = ''
|
||||||
|
return render_template('/admin/tests.html', form = form, display_title=display_title, error_none=error_none, filter=filter)
|
||||||
|
_tests = db.tests.find({})
|
||||||
|
if filter == 'active' or filter == '':
|
||||||
|
tests = [ test for test in _tests if test['expiry_date'].date() >= date.today() and test['start_date'].date() <= date.today() ]
|
||||||
|
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()]
|
||||||
|
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()]
|
||||||
|
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':
|
||||||
|
tests = _tests
|
||||||
|
display_title = 'All Exams'
|
||||||
|
error_none = 'There are no exams set up. You can create one using the Create Exam form.'
|
||||||
|
return render_template('/admin/tests.html', tests = tests, display_title=display_title, error_none=error_none, filter=filter)
|
||||||
|
|
||||||
|
@views.route('/tests/create/', methods=['POST'])
|
||||||
|
@admin_account_required
|
||||||
|
@login_required
|
||||||
|
def _tests():
|
||||||
from .forms import CreateTest
|
from .forms import CreateTest
|
||||||
form = CreateTest()
|
form = CreateTest()
|
||||||
form.time_limit.default='none'
|
form.time_limit.default='none'
|
||||||
form.process()
|
form.process()
|
||||||
if request.method == 'GET':
|
|
||||||
tests = decrypt_find(db.tests, {})
|
|
||||||
return render_template('/admin/tests.html', tests = tests, form = form)
|
|
||||||
if request.method == 'POST':
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
expiry = request.form.get('expiry')
|
start_date = request.form.get('start_date')
|
||||||
if datetime.strptime(expiry, '%Y-%m-%d').date() < date.today():
|
start_date = datetime.strptime(start_date, '%Y-%m-%d')
|
||||||
return jsonify({'error': 'The expiry date cannot be in the past.'}), 400
|
expiry_date = request.form.get('expiry_date')
|
||||||
|
expiry_date = datetime.strptime(expiry_date, '%Y-%m-%d')
|
||||||
|
errors = []
|
||||||
|
if start_date.date() < date.today():
|
||||||
|
errors.append('The start date cannot be in the past.')
|
||||||
|
if expiry_date.date() < date.today():
|
||||||
|
errors.append('The expiry date cannot be in the past.')
|
||||||
|
if expiry_date < start_date:
|
||||||
|
errors.append('The expiry date cannot be before the start date.')
|
||||||
|
if errors:
|
||||||
|
return jsonify({'error': errors}), 400
|
||||||
creator_id = get_id_from_cookie()
|
creator_id = get_id_from_cookie()
|
||||||
creator = decrypt_find_one(db.users, { '_id': creator_id } )['username']
|
creator = decrypt_find_one(db.users, { '_id': creator_id } )['username']
|
||||||
test = Test(
|
test = Test(
|
||||||
_id = uuid4().hex,
|
_id = uuid4().hex,
|
||||||
expiry = expiry,
|
start_date = start_date,
|
||||||
|
expiry_date = expiry_date,
|
||||||
time_limit = request.form.get('time_limit'),
|
time_limit = request.form.get('time_limit'),
|
||||||
creator = creator
|
creator = creator
|
||||||
)
|
)
|
||||||
@ -272,3 +313,9 @@ def tests():
|
|||||||
else:
|
else:
|
||||||
errors = [*form.expiry.errors, *form.time_limit.errors]
|
errors = [*form.expiry.errors, *form.time_limit.errors]
|
||||||
return jsonify({ 'error': errors}), 400
|
return jsonify({ 'error': errors}), 400
|
||||||
|
|
||||||
|
@views.route('/tests/delete/<_id>/')
|
||||||
|
def delete_test(_id):
|
||||||
|
if db.tests.find_one({'_id': _id}):
|
||||||
|
return Test(_id = _id).delete()
|
||||||
|
return abort(404)
|
Loading…
Reference in New Issue
Block a user