Completed client side time adjustment handling

Corrected error display bug
Removed redundant auth and models in quiz client app
This commit is contained in:
Vivek Santayana 2021-12-04 17:14:28 +00:00
parent cca01a6c2f
commit 93bf9e94fb
12 changed files with 64 additions and 54 deletions

View File

@ -41,18 +41,17 @@ 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):
user_code = secrets.token_hex(3).upper()
adjustment = { adjustment = {
'_id': uuid4().hex, user_code: time_adjustment
'user_code': secrets.token_hex(3).upper(),
'time_adjustment': time_adjustment
} }
if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': adjustment}},upsert=False): if db.tests.find_one_and_update({'_id': self._id}, {'$set': {'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"]}.') flash(f'Time adjustment for {time_adjustment} minutes has been added. This can be enabled using the user code {user_code}.')
return jsonify({'success': adjustment}) 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, user_code):
if db.tests.find_one_and_update({'_id': self._id}, {'$pull': {'time_adjustments': {'_id': _id} }}): if db.tests.find_one_and_update({'_id': self._id}, {'$unset': {f'time_adjustments.{user_code}': {}}}):
message = 'Time adjustment has been deleted.' message = 'Time adjustment has been deleted.'
flash(message, 'success') flash(message, 'success')
return jsonify({'success': message}) return jsonify({'success': message})

View File

@ -126,14 +126,16 @@ function error_response(response) {
</div> </div>
`); `);
} else if (response.responseJSON.error instanceof Array) { } else if (response.responseJSON.error instanceof Array) {
var output = ''
for (var i = 0; i < response.responseJSON.error.length; i ++) { for (var i = 0; i < response.responseJSON.error.length; i ++) {
$alert.html(` output += `
<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]}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
`); `;
$alert.html(output);
} }
} }
@ -205,17 +207,14 @@ $('.result-action-buttons').click(function(event){
// Script for Deleting Time Adjustment // Script for Deleting Time Adjustment
$('.adjustment-delete').click(function(event){ $('.adjustment-delete').click(function(event){
var _id = $(this).data('_id'); var user_code = $(this).data('user_code');
var location = window.location.href; var location = window.location.href;
location.replace('#', '') location = location.replace('#', '')
console.log(location)
console.log(_id)
$.ajax({ $.ajax({
url: location + 'delete-adjustment/', url: location + 'delete-adjustment/',
type: 'POST', type: 'POST',
data: JSON.stringify({'_id': _id}), data: JSON.stringify({'user_code': user_code}),
contentType: 'application/json', contentType: 'application/json',
success: function(response) { success: function(response) {
window.location.reload(); window.location.reload();

View File

@ -110,16 +110,16 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for entry in test.time_adjustments %} {% for key, value in test.time_adjustments.items() %}
<tr> <tr>
<td> <td>
{{ entry.user_code }} {{ key }}
</td> </td>
<td> <td>
{{ entry.time_adjustment }} {{ value }}
</td> </td>
<td> <td>
<a href="javascript::void(0);" class="btn btn-danger adjustment-delete" title="Delete Adjustment" data-_id="{{ entry._id }}" data-action="delete"> <a href="javascript::void(0);" class="btn btn-danger adjustment-delete" title="Delete Adjustment" data-user_code="{{ key }}">
<i class="bi bi-slash-circle-fill button-icon"></i> <i class="bi bi-slash-circle-fill button-icon"></i>
</a> </a>
</td> </td>

View File

@ -417,7 +417,7 @@ def view_test(_id):
return render_template('/admin/test.html', test = test, form = form) return render_template('/admin/test.html', test = test, form = form)
if request.method == 'POST': if request.method == 'POST':
if form.validate_on_submit(): if form.validate_on_submit():
time = request.form.get('time') time = int(request.form.get('time'))
return Test(_id=_id).add_time_adjustment(time) return Test(_id=_id).add_time_adjustment(time)
return jsonify({'error': form.time.errors }), 400 return jsonify({'error': form.time.errors }), 400
@ -425,8 +425,8 @@ def view_test(_id):
@admin_account_required @admin_account_required
@login_required @login_required
def delete_adjustment(_id): def delete_adjustment(_id):
adjustment_id = request.get_json()['_id'] user_code = request.get_json()['user_code']
return Test(_id=_id).remove_time_adjustment(adjustment_id) return Test(_id=_id).remove_time_adjustment(user_code)
@views.route('/results/') @views.route('/results/')
@admin_account_required @admin_account_required

View File

@ -43,10 +43,8 @@ if __name__ == '__main__':
from admin.auth import auth as admin_auth from admin.auth import auth as admin_auth
from admin.results import results from admin.results import results
from quiz.views import views as quiz_views from quiz.views import views as quiz_views
from quiz.auth import auth as quiz_auth
app.register_blueprint(quiz_views, url_prefix = '/') app.register_blueprint(quiz_views, url_prefix = '/')
app.register_blueprint(quiz_auth, url_prefix = '/')
app.register_blueprint(admin_views, url_prefix = '/admin/') app.register_blueprint(admin_views, url_prefix = '/admin/')
app.register_blueprint(admin_auth, url_prefix = '/admin/') app.register_blueprint(admin_auth, url_prefix = '/admin/')
app.register_blueprint(results, url_prefix = '/admin/results/') app.register_blueprint(results, url_prefix = '/admin/results/')

View File

@ -1,6 +0,0 @@
from flask import Blueprint
auth = Blueprint(
'quiz_auth',
__name__,
)

View File

@ -163,6 +163,18 @@ $("#btn-start-quiz").click(function(event){
time_remaining = get_time_remaining(); time_remaining = get_time_remaining();
clock = setInterval(timer, 1000); clock = setInterval(timer, 1000);
} }
if (response.time_adjustment > 0) {
const $alert = $("#alert-box");
$alert.html(
`<div class="alert alert-primary alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill" title="Alert"></i>
User code validated. Extra time of ${response.time_adjustment} minutes added to the exam time limit.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`
);
$alert.focus();
}
}, },
error: function(response) { error: function(response) {
error_response(response); error_response(response);
@ -507,7 +519,7 @@ function timer() {
if (time_remaining > 0) { if (time_remaining > 0) {
var timer_display = ''; var timer_display = '';
if (hours > 0) { if (hours > 0) {
timer_display = `${hours.toString()}: `; timer_display = `${hours.toString()}:`;
} }
if (minutes > 0 || hours > 0) { if (minutes > 0 || hours > 0) {
if (minutes < 10) { if (minutes < 10) {

View File

@ -37,11 +37,11 @@ $('form[name=form-quiz-start]').submit(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}
@ -49,14 +49,16 @@ function error_response(response) {
</div> </div>
`); `);
} else if (response.responseJSON.error instanceof Array) { } else if (response.responseJSON.error instanceof Array) {
var output = ''
for (var i = 0; i < response.responseJSON.error.length; i ++) { for (var i = 0; i < response.responseJSON.error.length; i ++) {
alert.html(` output += `
<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]}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
`); `;
$alert.html(output);
} }
} }
} }

View File

@ -8,6 +8,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="alert-box" tabindex="-1"></div>
<div class="container quiz-panel" id="quiz-settings" tabindex="-1"> <div class="container quiz-panel" id="quiz-settings" tabindex="-1">
<h1>Adjust Display Settings</h1> <h1>Adjust Display Settings</h1>
<div class="container quiz-start-text"> <div class="container quiz-start-text">
@ -255,7 +256,6 @@
<a href="#" class="btn btn-success quiz-button-submit" title="Submit Exam">Submit Exam</a> <a href="#" class="btn btn-success quiz-button-submit" title="Submit Exam">Submit Exam</a>
</div> </div>
</div> </div>
<div id="alert-box"></div>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script <script

View File

@ -36,7 +36,7 @@
<div class="col text-center"> <div class="col text-center">
<button class="btn btn-md btn-success btn-block" type="submit"> <button class="btn btn-md btn-success btn-block" type="submit">
<i class="bi bi-pencil-fill button-icon"></i> <i class="bi bi-pencil-fill button-icon"></i>
Start Exam Get Ready
</button> </button>
</div> </div>
</div> </div>

View File

@ -55,10 +55,12 @@ def start():
club = request.form.get('club') club = request.form.get('club')
test_code = request.form.get('test_code').replace('', '') test_code = request.form.get('test_code').replace('', '')
user_code = request.form.get('user_code') user_code = request.form.get('user_code')
user_code = None if user_code == '' else user_code user_code = None if user_code == '' else user_code.upper()
test = db.tests.find_one({'test_code': test_code}) test = db.tests.find_one({'test_code': test_code})
if not test: if not test:
return jsonify({'error': 'The exam code you entered is invalid.'}), 400 return jsonify({'error': 'The exam code you entered is invalid.'}), 400
if user_code and user_code not in test['time_adjustments']:
return jsonify({'error': f'The user code you entered is not valid.'}), 400
if test['expiry_date'] + timedelta(days=1) < datetime.utcnow(): if test['expiry_date'] + timedelta(days=1) < datetime.utcnow():
return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y")} UTC.'}), 400 return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y")} UTC.'}), 400
if test['start_date'] > datetime.utcnow(): if test['start_date'] > datetime.utcnow():
@ -88,12 +90,16 @@ def fetch_questions():
if not entry: if not entry:
return jsonify({'error': 'The data that the client sent to the server is invalid. This is possibly because you have already submitted your exam and have tried to access the page again.'}), 400 return jsonify({'error': 'The data that the client sent to the server is invalid. This is possibly because you have already submitted your exam and have tried to access the page again.'}), 400
test_code = entry['test_code'] test_code = entry['test_code']
# user_code = entry['user_code'] TODO Implement functionality for adjustments user_code = entry['user_code']
test = db.tests.find_one({'test_code' : test_code}) test = db.tests.find_one({'test_code' : test_code})
time_limit = test['time_limit'] time_limit = test['time_limit']
time_adjustment = 0
if time_limit: if time_limit:
_time_limit = int(time_limit) _time_limit = int(time_limit)
if user_code:
time_adjustment = test['time_adjustments'][user_code]
_time_limit += time_adjustment
adjustment = True
end_delta = timedelta(minutes=_time_limit) end_delta = timedelta(minutes=_time_limit)
end_time = datetime.utcnow() + end_delta end_time = datetime.utcnow() + end_delta
else: else:
@ -113,14 +119,14 @@ def fetch_questions():
return jsonify({ return jsonify({
'time_limit': end_time, 'time_limit': end_time,
'questions': questions, 'questions': questions,
'start_time': entry['start_time'] 'start_time': entry['start_time'],
'time_adjustment': time_adjustment
}) })
@views.route('/test/') @views.route('/test/')
def start_quiz(): def start_quiz():
_id = session.get('_id') _id = session.get('_id')
if not _id or not db.entries.find_one({'_id': _id}): if not _id or not db.entries.find_one({'_id': _id}):
print('Foo')
flash('Your log in was not recognised. Please sign in to the quiz again.', 'error') flash('Your log in was not recognised. Please sign in to the quiz again.', 'error')
return redirect(url_for('quiz_views.start')) return redirect(url_for('quiz_views.start'))
return render_template('quiz/client.html') return render_template('quiz/client.html')