Finished delete and data table fiew for tests
This commit is contained in:
		| @@ -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,91 +54,107 @@ | |||||||
|                             {% 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 %} |         <div class="form-container"> | ||||||
|         <script> |             <form name="form-create-test" class="form-signin"> | ||||||
|             $(document).ready(function() { |                 <h2 class="form-signin-heading">Create Exam</h2> | ||||||
|                 $('#active-test-table').DataTable({ |                 {{ form.hidden_tag() }} | ||||||
|                     'columnDefs': [ |                 <div class="form-date-input"> | ||||||
|                         {'sortable': false, 'targets': [1,3,5]}, |                     {{ form.start_date(placeholder="Enter Start Date", class_ = "datepicker") }} | ||||||
|                     {'searchable': false, 'targets': [3,5]} |                     {{ form.start_date.label }} | ||||||
|                     ], |                 </div> | ||||||
|                     'order': [[0, 'desc'], [2, 'asc']], |                 <div class="form-date-input"> | ||||||
|                     'buttons': [ |                     {{ form.expiry_date(placeholder="Enter Expiry Date", class_ = "datepicker") }} | ||||||
|                         'copy', 'excel', 'pdf' |                     {{ form.expiry_date.label }} | ||||||
|                     ], |                 </div> | ||||||
|                     'responsive': 'true', |                 <div class="form-select-input"> | ||||||
|                     'colReorder': 'true', |                     {{ form.time_limit(placeholder="Select Time Limit") }} | ||||||
|                     'fixedHeader': 'true' |                     {{ form.time_limit.label }} | ||||||
|                 }); |                 </div> | ||||||
|             } ); |                 {% include "admin/components/client-alerts.html" %} | ||||||
|             $('#test-table').show(); |                 <div class="container form-submission-button"> | ||||||
|             $(window).trigger('resize'); |                     <div class="row"> | ||||||
|         </script> |                         <div class="col text-center"> | ||||||
|     {% endblock %} |                             <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> | ||||||
|     <div class="form-container"> |                                 Create Exam | ||||||
|         <form name="form-create-test" class="form-signin"> |                             </button> | ||||||
|             <h2 class="form-signin-heading">Create Exam</h2> |                         </div> | ||||||
|             {{ 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> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </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,32 +243,79 @@ 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': |     if form.validate_on_submit(): | ||||||
|         tests = decrypt_find(db.tests, {}) |         start_date = request.form.get('start_date') | ||||||
|         return render_template('/admin/tests.html', tests = tests, form = form) |         start_date = datetime.strptime(start_date, '%Y-%m-%d') | ||||||
|     if request.method == 'POST': |         expiry_date = request.form.get('expiry_date') | ||||||
|         if form.validate_on_submit(): |         expiry_date = datetime.strptime(expiry_date, '%Y-%m-%d') | ||||||
|             expiry = request.form.get('expiry') |         errors = [] | ||||||
|             if datetime.strptime(expiry, '%Y-%m-%d').date() < date.today(): |         if start_date.date() < date.today(): | ||||||
|                 return jsonify({'error': 'The expiry date cannot be in the past.'}), 400 |             errors.append('The start date cannot be in the past.') | ||||||
|             creator_id = get_id_from_cookie() |         if  expiry_date.date() < date.today(): | ||||||
|             creator = decrypt_find_one(db.users, { '_id': creator_id } )['username'] |             errors.append('The expiry date cannot be in the past.') | ||||||
|             test = Test( |         if expiry_date < start_date: | ||||||
|                 _id = uuid4().hex, |             errors.append('The expiry date cannot be before the start date.') | ||||||
|                 expiry = expiry, |         if errors: | ||||||
|                 time_limit = request.form.get('time_limit'), |             return jsonify({'error': errors}), 400 | ||||||
|                 creator = creator |         creator_id = get_id_from_cookie() | ||||||
|             ) |         creator = decrypt_find_one(db.users, { '_id': creator_id } )['username'] | ||||||
|             test.create() |         test = Test( | ||||||
|             return jsonify({'success': 'New exam created.'}), 200 |             _id = uuid4().hex, | ||||||
|         else: |             start_date = start_date, | ||||||
|             errors = [*form.expiry.errors, *form.time_limit.errors] |             expiry_date = expiry_date, | ||||||
|             return jsonify({ 'error': errors}), 400 |             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