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