Completed client side time adjustment handling
Corrected error display bug Removed redundant auth and models in quiz client app
This commit is contained in:
		| @@ -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}) | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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/') | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| from flask import Blueprint |  | ||||||
|  |  | ||||||
| auth = Blueprint( |  | ||||||
|     'quiz_auth', |  | ||||||
|     __name__, |  | ||||||
| ) |  | ||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user