Added server and admin-side time limit adjustments

This commit is contained in:
Vivek Santayana 2021-12-04 15:41:47 +00:00
parent 239c29b1f5
commit 52908a548c
7 changed files with 270 additions and 33 deletions

View File

@ -41,13 +41,14 @@ class Test:
return jsonify({'error': f'Could not create exam. An error occurred.'}), 400
def add_time_adjustment(self, time_adjustment):
code = {
adjustment = {
'_id': uuid4().hex,
'user_code': secrets.token_hex(2).upper(),
'user_code': secrets.token_hex(3).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})
if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': adjustment}},upsert=False):
flash(f'Time adjustment for {adjustment["time_adjustment"]} minutes has been added. This can be enabled using the user code {adjustment["user_code"]}.')
return jsonify({'success': adjustment})
return jsonify({'error': 'Failed to add the time adjustment. An error occurred.'}), 400
def remove_time_adjustment(self, _id):

View File

@ -214,11 +214,15 @@ table.dataTable {
font-size: 14pt;
}
.result-action-buttons {
.result-action-buttons, .test-action {
margin: 5px auto;
width: fit-content;
}
.accordion-item {
background-color: unset;
}
/* Fallback for Edge
-------------------------------------------------- */
@supports (-ms-ime-align: auto) {

View File

@ -63,22 +63,27 @@ $('form[name=form-upload-questions]').submit(function(event) {
});
// Edit and Delete Test Button Handlers
$('.delete-test').click(function(event) {
$('.test-action').click(function(event) {
let _id = $(this).data('_id')
let _id = $(this).data('_id');
let action = $(this).data('action');
$.ajax({
url: `/admin/tests/delete/`,
type: 'POST',
data: JSON.stringify({'_id': _id}),
contentType: 'application/json',
success: function(response) {
window.location.href = '/admin/tests/';
},
error: function(response){
error_response(response);
},
});
if (action == 'delete') {
$.ajax({
url: `/admin/tests/delete/`,
type: 'POST',
data: JSON.stringify({'_id': _id}),
contentType: 'application/json',
success: function(response) {
window.location.href = '/admin/tests/';
},
error: function(response){
error_response(response);
},
});
} else if (action == 'edit') {
window.location.href = `/admin/test/${_id}/`
}
event.preventDefault();
});
@ -109,11 +114,11 @@ $('.edit-question-dataset').click(function(event) {
function error_response(response) {
var alert = $("#alert-box");
alert.html('');
const $alert = $("#alert-box");
$alert.html('');
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
alert.html(`
$alert.html(`
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
${response.responseJSON.error}
@ -122,7 +127,7 @@ function error_response(response) {
`);
} else if (response.responseJSON.error instanceof Array) {
for (var i = 0; i < response.responseJSON.error.length; i ++) {
alert.html(`
$alert.html(`
<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]}
@ -131,6 +136,8 @@ function error_response(response) {
`);
}
}
$alert.focus()
}
// Dismiss Cookie Alert
@ -152,13 +159,12 @@ $('#dismiss-cookie-alert').click(function(event){
})
event.preventDefault();
})
// Script for Resutlt Actions
// Script for Result Actions
$('.result-action-buttons').click(function(event){
var _id = $(this).data('_id')
var _id = $(this).data('_id');
if ($(this).data('result-action') == 'generate') {
$.ajax({
@ -192,4 +198,32 @@ $('.result-action-buttons').click(function(event){
},
});
}
event.preventDefault();
});
// Script for Deleting Time Adjustment
$('.adjustment-delete').click(function(event){
var _id = $(this).data('_id');
var location = window.location.href;
location.replace('#', '')
console.log(location)
console.log(_id)
$.ajax({
url: location + 'delete-adjustment/',
type: 'POST',
data: JSON.stringify({'_id': _id}),
contentType: 'application/json',
success: function(response) {
window.location.reload();
},
error: function(response){
error_response(response);
},
});
event.preventDefault();
});

View File

@ -1 +1 @@
<div id="alert-box"></div>
<div id="alert-box" tabindex="-1"></div>

View File

@ -0,0 +1,180 @@
{% extends "admin/components/base.html" %}
{% block title %} SKA Referee Test | Edit Exam {% endblock %}
{% block content %}
<h1>Edit Exam</h1>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6">
<ul class="list-group">
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Exam Code</h5>
</div>
<h2>
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
</h2>
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Dataset</h5>
</div>
{{ test.dataset }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Created By</h5>
</div>
{{ test.creator }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Date Created</h5>
</div>
{{ test.date_created.strftime('%d %b %Y') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Expiry Date</h5>
</div>
{{ test.start_date.strftime('%d %b %Y') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Expiry Date</h5>
</div>
{{ test.expiry_date.strftime('%d %b %Y') }}
</li>
<li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Time Limit</h5>
</div>
{% if test.time_limit == None -%}
None
{% elif test.time_limit == 60 -%}
1 hour
{% elif test.time_limit == 90 -%}
1 hour 30 min
{% elif test.time_limit == 120 -%}
2 hours
{% else -%}
{{ test.time_limit }}
{% endif %}
</li>
<div class="accordion" id="test-info-detail">
{% if 'entries' in test and test.entries|length > 0 %}
<div class="accordion-item">
<h2 class="accordion-header" id="test-entries">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-entries-list" aria-expanded="false" aria-controls="test-entries-list">
List Test Entries
</button>
</h2>
<div id="test-entries-list" class="accordion-collapse collapse" aria-labelledby="test-entries" data-bs-parent="#test-info-detail">
<div class="accordion-body">
<table class="table table-striped">
<tbody>
{% for entry in test.entries %}
<tr>
<td>
<a href="{{ url_for('admin_views.view_entry', _id=entry) }}" >Entry {{ loop.index }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% if 'time_adjustments' in test and test.time_adjustments|length > 0 %}
<div class="accordion-item">
<h2 class="accordion-header" id="test-adjustments">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-adjustments-list" aria-expanded="false" aria-controls="test-adjustments-list">
List Time Adjustments
</button>
</h2>
<div id="test-adjustments-list" class="accordion-collapse collapse" aria-labelledby="test-adjustments" data-bs-parent="#test-info-detail">
<div class="accordion-body">
<table class="table table-striped">
<thead>
<tr>
<th>
User Code
</th>
<th>
Adjustment (Minutes)
</th>
<th>
Delete
</th>
</tr>
</thead>
<tbody>
{% for entry in test.time_adjustments %}
<tr>
<td>
{{ entry.user_code }}
</td>
<td>
{{ entry.time_adjustment }}
</td>
<td>
<a href="javascript::void(0);" class="btn btn-danger adjustment-delete" title="Delete Adjustment" data-_id="{{ entry._id }}" data-action="delete">
<i class="bi bi-slash-circle-fill button-icon"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% if not test.time_limit == None %}
<div class="accordion-item">
<h2 class="accordion-header" id="test-add-adjustments">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-adjustments-add" aria-expanded="false" aria-controls="test-adjustments-add">
Add Time Adjustments
</button>
</h2>
<div id="test-adjustments-add" class="accordion-collapse collapse" aria-labelledby="test-add-adjustments" data-bs-parent="#test-info-detail">
<div class="accordion-body">
<form name="form-add-adjustment" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="">
{{ form.hidden_tag() }}
<div class="form-label-group">
{{ form.time(class_="form-control", placeholder="Enter Username") }}
{{ form.time.label }}
</div>
<div class="container form-submission-button">
<div class="row">
<div class="col text-center">
<button title="Add Time Adjustment" class="btn btn-md btn-success btn-block" type="submit">
<i class="bi bi-clock-history button-icon"></i>
Add Time Adjustment
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endif %}
</div>
</ul>
<div class="row">
{% include "admin/components/client-alerts.html" %}
</div>
<div class="container justify-content-center">
<div class="row">
<a href="#" class="btn btn-danger test-action" data-action="delete" data-_id="{{ test._id }}">
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
Delete Exam
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -60,17 +60,19 @@
<td class="row-actions">
<a
href="#"
class="btn btn-primary edit-test"
class="btn btn-primary test-action"
data-_id="{{test._id}}"
title="Edit Exam"
data-action="edit"
>
<i class="bi bi-file-earmark-text-fill button-icon"></i>
</a>
<a
href="#"
class="btn btn-danger delete-test"
class="btn btn-danger test-action"
data-_id="{{test._id}}"
title="Delete Exam"
data-action="delete"
>
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
</button>

View File

@ -407,10 +407,26 @@ def delete_test():
@views.route('/test/<_id>/', methods=['GET','POST'])
@admin_account_required
@login_required
def view_test(_id, filter=''):
test = db.tests.find_one({'_id':_id})
if not test:
return abort(404)
def view_test(_id):
from .models.forms import AddTimeAdjustment
form = AddTimeAdjustment()
test = decrypt_find_one(db.tests, {'_id': _id})
if request.method == 'GET':
if not test:
return abort(404)
return render_template('/admin/test.html', test = test, form = form)
if request.method == 'POST':
if form.validate_on_submit():
time = request.form.get('time')
return Test(_id=_id).add_time_adjustment(time)
return jsonify({'error': form.time.errors }), 400
@views.route('/test/<_id>/delete-adjustment/', methods = ['POST'])
@admin_account_required
@login_required
def delete_adjustment(_id):
adjustment_id = request.get_json()['_id']
return Test(_id=_id).remove_time_adjustment(adjustment_id)
@views.route('/results/')
@admin_account_required