Finished delete and data table fiew for tests
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
			
		||||
from flask_wtf import FlaskForm
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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.')])
 | 
			
		||||
 | 
			
		||||
class CreateTest(FlaskForm):
 | 
			
		||||
    start_date = DateField('Start Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() )
 | 
			
		||||
    time_options = [
 | 
			
		||||
        ('none', 'None'),
 | 
			
		||||
        ('60', '1 hour'),
 | 
			
		||||
        ('90', '1 hour 30 minutes'),
 | 
			
		||||
        ('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)
 | 
			
		||||
@@ -2,22 +2,25 @@ import secrets
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from flask import flash, jsonify
 | 
			
		||||
import secrets
 | 
			
		||||
 | 
			
		||||
from main import db
 | 
			
		||||
from security import encrypt, decrypt
 | 
			
		||||
from security import encrypt
 | 
			
		||||
 | 
			
		||||
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.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.creator = creator
 | 
			
		||||
    
 | 
			
		||||
    def create(self):
 | 
			
		||||
        test = {
 | 
			
		||||
            '_id': self._id,
 | 
			
		||||
            'date': datetime.today(),
 | 
			
		||||
            'expiry': self.expiry,
 | 
			
		||||
            'date_created': datetime.today(),
 | 
			
		||||
            'start_date': self.start_date,
 | 
			
		||||
            'expiry_date': self.expiry_date,
 | 
			
		||||
            'time_limit': self.time_limit,
 | 
			
		||||
            'creator': encrypt(self.creator),
 | 
			
		||||
            'test_code': secrets.token_hex(6).upper()
 | 
			
		||||
@@ -27,19 +30,66 @@ class Test:
 | 
			
		||||
            return jsonify({'success': test}), 200
 | 
			
		||||
        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 = {
 | 
			
		||||
            '_id': uuid4().hex,
 | 
			
		||||
            'user_code': user_code,
 | 
			
		||||
            'user_code': secrets.token_hex(2).upper(),
 | 
			
		||||
            'time_adjustment': time_adjustment
 | 
			
		||||
        }
 | 
			
		||||
        if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': code}},upsert=False):
 | 
			
		||||
            return jsonify({'success': code})
 | 
			
		||||
        else:
 | 
			
		||||
            return jsonify({'error': 'An error occurred.'}), 400
 | 
			
		||||
        return jsonify({'error': 'Failed to add the time adjustment. 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):
 | 
			
		||||
        return '—'.join([test_code[:4], test_code[4:8], test_code[8:]])
 | 
			
		||||
    
 | 
			
		||||
    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 {
 | 
			
		||||
    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 {
 | 
			
		||||
@@ -197,6 +210,10 @@ table.dataTable {
 | 
			
		||||
    z-index: -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-icon {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Fallback for Edge
 | 
			
		||||
-------------------------------------------------- */
 | 
			
		||||
@supports (-ms-ime-align: auto) {
 | 
			
		||||
 
 | 
			
		||||
@@ -350,12 +350,49 @@ $('form[name=form-update-account]').submit(function(event) {
 | 
			
		||||
    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) {
 | 
			
		||||
    
 | 
			
		||||
    var $form = $(this);
 | 
			
		||||
    var alert = document.getElementById('alert-box');
 | 
			
		||||
    var data = $form.serialize();
 | 
			
		||||
    console.log(data)
 | 
			
		||||
    alert.innerHTML = ''
 | 
			
		||||
 | 
			
		||||
    $.ajax({
 | 
			
		||||
@@ -364,10 +401,9 @@ $('form[name=form-create-test]').submit(function(event) {
 | 
			
		||||
        data: data,
 | 
			
		||||
        dataType: 'json',
 | 
			
		||||
        success: function(response) {
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
            window.location.href = '/admin/tests/';
 | 
			
		||||
        },
 | 
			
		||||
        error: function(response) {
 | 
			
		||||
            console.log(response.responseJSON)
 | 
			
		||||
            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">
 | 
			
		||||
@@ -396,8 +432,6 @@ $('form[name=form-create-test]').submit(function(event) {
 | 
			
		||||
// Dismiss Cookie Alert
 | 
			
		||||
$('#dismiss-cookie-alert').click(function(event){
 | 
			
		||||
 | 
			
		||||
    console.log('Foo')
 | 
			
		||||
 | 
			
		||||
    $.ajax({
 | 
			
		||||
        url: '/cookies/',
 | 
			
		||||
        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 content %}
 | 
			
		||||
    <h1>Manage Users</h1>
 | 
			
		||||
 | 
			
		||||
    <table id="user-table" class="table table-striped" style="width:100%">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
@@ -50,9 +49,7 @@
 | 
			
		||||
                            class="btn btn-primary"
 | 
			
		||||
                            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">
 | 
			
		||||
                                <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>
 | 
			
		||||
                            <i class="bi bi-person-lines-fill button-icon"></i>
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <a
 | 
			
		||||
                            href="
 | 
			
		||||
@@ -66,16 +63,13 @@
 | 
			
		||||
                            title="Delete User"
 | 
			
		||||
                            {% 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">
 | 
			
		||||
                                <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>
 | 
			
		||||
                            <i class="bi bi-person-x-fill button-icon"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <div class="form-container">
 | 
			
		||||
        <form name="form-create-user" class="form-signin">
 | 
			
		||||
            <h2 class="form-signin-heading">Create User</h2>
 | 
			
		||||
@@ -100,10 +94,7 @@
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col text-center">
 | 
			
		||||
                        <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">
 | 
			
		||||
                                <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>
 | 
			
		||||
                            <i class="bi bi-person-plus-fill button-icon"></i>
 | 
			
		||||
                            Create User
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,11 @@
 | 
			
		||||
{% block title %} SKA Referee Test | Manage Exams {% endblock %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <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%">
 | 
			
		||||
            <thead>
 | 
			
		||||
                <caption>
 | 
			
		||||
                    Active Tests
 | 
			
		||||
                </caption>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th data-priority="1">
 | 
			
		||||
                        Start Date
 | 
			
		||||
@@ -22,7 +21,7 @@
 | 
			
		||||
                        Time Limit
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="4">
 | 
			
		||||
                        Results
 | 
			
		||||
                        Entries
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="1">
 | 
			
		||||
                        Actions
 | 
			
		||||
@@ -30,16 +29,16 @@
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                {% for test in active_tests %}
 | 
			
		||||
                {% for test in tests %}
 | 
			
		||||
                    <tr class="user-table-row">
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ test.date.strftime('%d %b %Y') }}
 | 
			
		||||
                            {{ test.start_date.strftime('%d %b %Y') }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ test.expiry }}
 | 
			
		||||
                            {{ test.expiry_date.strftime('%d %b %Y') }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if test.time_limit == None -%}
 | 
			
		||||
@@ -55,91 +54,107 @@
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ test.creator }}
 | 
			
		||||
                            {{ test.attempts|length }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td class="test-row-actions">
 | 
			
		||||
                            <a
 | 
			
		||||
                                href="#"
 | 
			
		||||
                                class="btn btn-primary"
 | 
			
		||||
                                class="btn btn-primary edit-test"
 | 
			
		||||
                                data-_id="{{test._id}}"
 | 
			
		||||
                                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">
 | 
			
		||||
                                    <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>
 | 
			
		||||
                                <i class="bi bi-file-earmark-text-fill button-icon"></i>
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <a
 | 
			
		||||
                                href="#"
 | 
			
		||||
                                class="btn btn-danger"
 | 
			
		||||
                                class="btn btn-danger delete-test"
 | 
			
		||||
                                data-_id="{{test._id}}"
 | 
			
		||||
                                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">
 | 
			
		||||
                                    <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>
 | 
			
		||||
                                <i class="bi bi-file-earmark-excel-fill button-icon"></i>
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    {% else %}
 | 
			
		||||
    {% elif not filter == 'create' %}
 | 
			
		||||
            <div class="alert alert-primary alert-db-empty">
 | 
			
		||||
                <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>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
        
 | 
			
		||||
    {% 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">
 | 
			
		||||
        <form name="form-create-test" class="form-signin">
 | 
			
		||||
            <h2 class="form-signin-heading">Create Exam</h2>
 | 
			
		||||
            {{ form.hidden_tag() }}
 | 
			
		||||
            <div class="form-date-input">
 | 
			
		||||
                {{ form.start_date(placeholder="Enter Start Date", class_ = "datepicker") }}
 | 
			
		||||
                {{ form.start_date.label }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-date-input">
 | 
			
		||||
                {{ form.expiry_date(placeholder="Enter Expiry Date", class_ = "datepicker") }}
 | 
			
		||||
                {{ form.expiry_date.label }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-select-input">
 | 
			
		||||
                {{ form.time_limit(placeholder="Select Time Limit") }}
 | 
			
		||||
                {{ form.time_limit.label }}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
            <div class="container form-submission-button">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col text-center">
 | 
			
		||||
                        <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">
 | 
			
		||||
                                <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
 | 
			
		||||
                        </button>
 | 
			
		||||
    {% if form %}
 | 
			
		||||
        <div class="form-container">
 | 
			
		||||
            <form name="form-create-test" class="form-signin">
 | 
			
		||||
                <h2 class="form-signin-heading">Create Exam</h2>
 | 
			
		||||
                {{ form.hidden_tag() }}
 | 
			
		||||
                <div class="form-date-input">
 | 
			
		||||
                    {{ form.start_date(placeholder="Enter Start Date", class_ = "datepicker") }}
 | 
			
		||||
                    {{ form.start_date.label }}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-date-input">
 | 
			
		||||
                    {{ form.expiry_date(placeholder="Enter Expiry Date", class_ = "datepicker") }}
 | 
			
		||||
                    {{ form.expiry_date.label }}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-select-input">
 | 
			
		||||
                    {{ form.time_limit(placeholder="Select Time Limit") }}
 | 
			
		||||
                    {{ form.time_limit.label }}
 | 
			
		||||
                </div>
 | 
			
		||||
                {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
                <div class="container form-submission-button">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col text-center">
 | 
			
		||||
                            <button title="Create Exam" class="btn btn-md btn-success btn-block" type="submit">
 | 
			
		||||
                                <i class="bi bi-file-earmark-plus-fill button-icon"></i>
 | 
			
		||||
                                Create Exam
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% 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,32 +243,79 @@ def questions():
 | 
			
		||||
def upload_questions():
 | 
			
		||||
    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
 | 
			
		||||
@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
 | 
			
		||||
    form = CreateTest()
 | 
			
		||||
    form.time_limit.default='none'
 | 
			
		||||
    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():
 | 
			
		||||
            expiry = request.form.get('expiry')
 | 
			
		||||
            if datetime.strptime(expiry, '%Y-%m-%d').date() < date.today():
 | 
			
		||||
                return jsonify({'error': 'The expiry date cannot be in the past.'}), 400
 | 
			
		||||
            creator_id = get_id_from_cookie()
 | 
			
		||||
            creator = decrypt_find_one(db.users, { '_id': creator_id } )['username']
 | 
			
		||||
            test = Test(
 | 
			
		||||
                _id = uuid4().hex,
 | 
			
		||||
                expiry = expiry,
 | 
			
		||||
                time_limit = request.form.get('time_limit'),
 | 
			
		||||
                creator = creator
 | 
			
		||||
            )
 | 
			
		||||
            test.create()
 | 
			
		||||
            return jsonify({'success': 'New exam created.'}), 200
 | 
			
		||||
        else:
 | 
			
		||||
            errors = [*form.expiry.errors, *form.time_limit.errors]
 | 
			
		||||
            return jsonify({ 'error': errors}), 400
 | 
			
		||||
    if form.validate_on_submit():
 | 
			
		||||
        start_date = request.form.get('start_date')
 | 
			
		||||
        start_date = datetime.strptime(start_date, '%Y-%m-%d')
 | 
			
		||||
        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 = decrypt_find_one(db.users, { '_id': creator_id } )['username']
 | 
			
		||||
        test = Test(
 | 
			
		||||
            _id = uuid4().hex,
 | 
			
		||||
            start_date = start_date,
 | 
			
		||||
            expiry_date = expiry_date,
 | 
			
		||||
            time_limit = request.form.get('time_limit'),
 | 
			
		||||
            creator = creator
 | 
			
		||||
        )
 | 
			
		||||
        test.create()
 | 
			
		||||
        return jsonify({'success': 'New exam created.'}), 200
 | 
			
		||||
    else:
 | 
			
		||||
        errors = [*form.expiry.errors, *form.time_limit.errors]
 | 
			
		||||
        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)
 | 
			
		||||
		Reference in New Issue
	
	Block a user