Added server and admin-side time limit adjustments
This commit is contained in:
parent
4b603b70a0
commit
cca01a6c2f
@ -41,13 +41,14 @@ class Test:
|
|||||||
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_time_adjustment(self, time_adjustment):
|
def add_time_adjustment(self, time_adjustment):
|
||||||
code = {
|
adjustment = {
|
||||||
'_id': uuid4().hex,
|
'_id': uuid4().hex,
|
||||||
'user_code': secrets.token_hex(2).upper(),
|
'user_code': secrets.token_hex(3).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': adjustment}},upsert=False):
|
||||||
return jsonify({'success': code})
|
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
|
return jsonify({'error': 'Failed to add the time adjustment. An error occurred.'}), 400
|
||||||
|
|
||||||
def remove_time_adjustment(self, _id):
|
def remove_time_adjustment(self, _id):
|
||||||
|
@ -214,11 +214,15 @@ table.dataTable {
|
|||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-action-buttons {
|
.result-action-buttons, .test-action {
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion-item {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fallback for Edge
|
/* Fallback for Edge
|
||||||
-------------------------------------------------- */
|
-------------------------------------------------- */
|
||||||
@supports (-ms-ime-align: auto) {
|
@supports (-ms-ime-align: auto) {
|
||||||
|
@ -63,22 +63,27 @@ $('form[name=form-upload-questions]').submit(function(event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Edit and Delete Test Button Handlers
|
// 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({
|
if (action == 'delete') {
|
||||||
url: `/admin/tests/delete/`,
|
$.ajax({
|
||||||
type: 'POST',
|
url: `/admin/tests/delete/`,
|
||||||
data: JSON.stringify({'_id': _id}),
|
type: 'POST',
|
||||||
contentType: 'application/json',
|
data: JSON.stringify({'_id': _id}),
|
||||||
success: function(response) {
|
contentType: 'application/json',
|
||||||
window.location.href = '/admin/tests/';
|
success: function(response) {
|
||||||
},
|
window.location.href = '/admin/tests/';
|
||||||
error: function(response){
|
},
|
||||||
error_response(response);
|
error: function(response){
|
||||||
},
|
error_response(response);
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} else if (action == 'edit') {
|
||||||
|
window.location.href = `/admin/test/${_id}/`
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
@ -109,11 +114,11 @@ $('.edit-question-dataset').click(function(event) {
|
|||||||
|
|
||||||
function error_response(response) {
|
function error_response(response) {
|
||||||
|
|
||||||
var alert = $("#alert-box");
|
const $alert = $("#alert-box");
|
||||||
alert.html('');
|
$alert.html('');
|
||||||
|
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
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">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
${response.responseJSON.error}
|
${response.responseJSON.error}
|
||||||
@ -122,7 +127,7 @@ function error_response(response) {
|
|||||||
`);
|
`);
|
||||||
} else if (response.responseJSON.error instanceof Array) {
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
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">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
${response.responseJSON.error[i]}
|
${response.responseJSON.error[i]}
|
||||||
@ -131,6 +136,8 @@ function error_response(response) {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$alert.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss Cookie Alert
|
// Dismiss Cookie Alert
|
||||||
@ -152,13 +159,12 @@ $('#dismiss-cookie-alert').click(function(event){
|
|||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Script for Resutlt Actions
|
// Script for Result Actions
|
||||||
$('.result-action-buttons').click(function(event){
|
$('.result-action-buttons').click(function(event){
|
||||||
|
|
||||||
var _id = $(this).data('_id')
|
var _id = $(this).data('_id');
|
||||||
|
|
||||||
if ($(this).data('result-action') == 'generate') {
|
if ($(this).data('result-action') == 'generate') {
|
||||||
$.ajax({
|
$.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();
|
||||||
});
|
});
|
@ -1 +1 @@
|
|||||||
<div id="alert-box"></div>
|
<div id="alert-box" tabindex="-1"></div>
|
180
ref-test/admin/templates/admin/test.html
Normal file
180
ref-test/admin/templates/admin/test.html
Normal 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 %}
|
@ -60,17 +60,19 @@
|
|||||||
<td class="row-actions">
|
<td class="row-actions">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-primary edit-test"
|
class="btn btn-primary test-action"
|
||||||
data-_id="{{test._id}}"
|
data-_id="{{test._id}}"
|
||||||
title="Edit Exam"
|
title="Edit Exam"
|
||||||
|
data-action="edit"
|
||||||
>
|
>
|
||||||
<i class="bi bi-file-earmark-text-fill button-icon"></i>
|
<i class="bi bi-file-earmark-text-fill button-icon"></i>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-danger delete-test"
|
class="btn btn-danger test-action"
|
||||||
data-_id="{{test._id}}"
|
data-_id="{{test._id}}"
|
||||||
title="Delete Exam"
|
title="Delete Exam"
|
||||||
|
data-action="delete"
|
||||||
>
|
>
|
||||||
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -407,10 +407,26 @@ def delete_test():
|
|||||||
@views.route('/test/<_id>/', methods=['GET','POST'])
|
@views.route('/test/<_id>/', methods=['GET','POST'])
|
||||||
@admin_account_required
|
@admin_account_required
|
||||||
@login_required
|
@login_required
|
||||||
def view_test(_id, filter=''):
|
def view_test(_id):
|
||||||
test = db.tests.find_one({'_id':_id})
|
from .models.forms import AddTimeAdjustment
|
||||||
if not test:
|
form = AddTimeAdjustment()
|
||||||
return abort(404)
|
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/')
|
@views.route('/results/')
|
||||||
@admin_account_required
|
@admin_account_required
|
||||||
|
Loading…
Reference in New Issue
Block a user