from flask import Blueprint, render_template, request, redirect, jsonify, session, abort, flash from flask.helpers import url_for from datetime import datetime, timedelta from uuid import uuid4 import os from json import loads from pymongo.collection import ReturnDocument from main import app, db from common.security import encrypt from common.data_tools import generate_questions, evaluate_answers from common.security.database import decrypt_find_one views = Blueprint( 'quiz_views', __name__, static_url_path='', template_folder='templates', static_folder='static' ) @views.route('/') @views.route('/home/') def home(): _id = session.get('_id') if _id and db.entries.find_one({'_id': _id}): return redirect(url_for('quiz_views.start_quiz')) return render_template('/quiz/index.html') @views.route('/start/', methods = ['GET', 'POST']) def start(): from .forms import StartQuiz form = StartQuiz() if request.method == 'GET': _id = session.get('_id') if _id and db.entries.find_one({'_id': _id}): return redirect(url_for('quiz_views.start_quiz')) return render_template('/quiz/start-quiz.html', form=form) if request.method == 'POST': if form.validate_on_submit(): name = { 'first_name': request.form.get('first_name'), 'surname': request.form.get('surname') } email = request.form.get('email') club = request.form.get('club') test_code = request.form.get('test_code').replace('—', '') user_code = request.form.get('user_code') user_code = None if user_code == '' else user_code test = db.tests.find_one({'test_code': test_code}) if not test: return jsonify({'error': 'The exam code you entered is invalid.'}), 400 if test['expiry_date'] < datetime.utcnow(): return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y")}.'}), 400 if test['start_date'] > datetime.utcnow(): return jsonify({'error': f'The exam has not yet opened. Your exam code will be valid from {test["start_date"].strftime("%d %b %Y %H:%M")}.'}), 400 entry = { '_id': uuid4().hex, 'name': encrypt(name), 'email': encrypt(email), 'club': encrypt(club), 'test_code': test_code, 'user_code': user_code } if db.entries.insert(entry): session['_id'] = entry['_id'] return jsonify({ 'success': 'Received and validated test and/or user code. Redirecting to test client.', '_id': entry['_id'] }), 200 else: errors = [*form.errors] return jsonify({ 'error': errors}), 400 @views.route('/api/questions/', methods=['POST']) def fetch_questions(): _id = request.get_json()['_id'] entry = db.entries.find_one({'_id': _id}) if not entry: return abort(404) test_code = entry['test_code'] # user_code = entry['user_code'] TODO Implement functionality for adjustments test = db.tests.find_one({'test_code' : test_code}) time_limit = test['time_limit'] if time_limit: _time_limit = int(time_limit) end_delta = timedelta(minutes=_time_limit) end_time = datetime.utcnow() + end_delta else: end_time = None update = { 'start_time': datetime.utcnow(), 'status': 'started', 'end_time': end_time } entry = db.entries.find_one_and_update({'_id': _id}, {'$set': update}, upsert=False, return_document=ReturnDocument.AFTER) dataset = test['dataset'] dataset_path = os.path.join(app.config['DATA_FILE_DIRECTORY'], dataset) with open(dataset_path, 'r') as data_file: data = loads(data_file.read()) questions = generate_questions(data) return jsonify({ 'time_limit': end_time, 'questions': questions, 'start_time': entry['start_time'] }) @views.route('/test/') def start_quiz(): _id = session.get('_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') return redirect(url_for('quiz_views.start')) return render_template('quiz/client.html') @views.route('/api/submit/', methods=['POST']) def submit_quiz(): _id = request.get_json()['_id'] answers = request.get_json()['answers'] entry = db.entries.find_one({'_id': _id}) if not entry: return jsonify('Unrecognised ID', 'error'), 400 status = 'submitted' if entry['end_time']: if datetime.utcnow() > entry['end_time'] + timedelta(minutes=2): status = 'late' test_code = entry['test_code'] test = db.tests.find_one({'test_code' : test_code}) dataset = test['dataset'] dataset_path = os.path.join(app.config['DATA_FILE_DIRECTORY'], dataset) with open(dataset_path, 'r') as _dataset: data = loads(_dataset.read()) results = evaluate_answers(data, answers) entry = db.entries.find_one_and_update({'_id': _id}, {'$set': { 'status': status, 'submission_time': datetime.utcnow(), 'results': results, 'answers': answers }}) return jsonify({ 'success': 'Your submission has been processed. Redirecting you to receive your results.', '_id': _id }), 200 @views.route('/result/') def result(): _id = session.get('_id') entry = decrypt_find_one(db.entries, {'_id': _id}) if not entry: return abort(404) score = round(100*entry['results']['score']/entry['results']['max']) tags_low = { tag: tag_result['max'] - tag_result['scored'] for tag, tag_result in entry['results']['tags'].items() } sorted_low = sorted(tags_low.items(), key=lambda x: x[1], reverse=True) show_low_tags = sorted_low[0:3] return render_template('/quiz/result.html', entry=entry, score=score, show_low_tags=show_low_tags) @views.route('/privacy/') def privacy(): return render_template('/quiz/privacy.html')