2021-11-30 03:11:28 +00:00
from flask import Blueprint , render_template , request , redirect , jsonify , session , abort , flash
2021-11-28 18:17:50 +00:00
from flask . helpers import url_for
2021-11-30 18:06:24 +00:00
from datetime import datetime , timedelta
2021-11-25 23:12:20 +00:00
from uuid import uuid4
2021-11-28 18:17:50 +00:00
import os
from json import loads
2021-12-01 00:46:21 +00:00
from flask_mail import Message
2021-11-25 23:12:20 +00:00
2021-11-30 18:06:24 +00:00
from pymongo . collection import ReturnDocument
2021-11-28 02:30:46 +00:00
from common . security import encrypt
2021-11-30 18:06:24 +00:00
from common . data_tools import generate_questions , evaluate_answers
from common . security . database import decrypt_find_one
2021-11-23 13:00:03 +00:00
views = Blueprint (
' quiz_views ' ,
__name__ ,
2021-11-24 17:17:56 +00:00
static_url_path = ' ' ,
2021-11-23 13:00:03 +00:00
template_folder = ' templates ' ,
static_folder = ' static '
)
@views.route ( ' / ' )
@views.route ( ' /home/ ' )
def home ( ) :
2021-12-05 03:49:31 +00:00
from main import db
2021-11-30 18:06:24 +00:00
_id = session . get ( ' _id ' )
if _id and db . entries . find_one ( { ' _id ' : _id } ) :
return redirect ( url_for ( ' quiz_views.start_quiz ' ) )
2021-11-24 17:17:56 +00:00
return render_template ( ' /quiz/index.html ' )
2021-12-01 00:46:21 +00:00
@views.route ( ' /instructions/ ' )
def instructions ( ) :
2021-12-05 03:49:31 +00:00
from main import db
2021-12-01 00:46:21 +00:00
_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/instructions.html ' )
2021-11-24 17:17:56 +00:00
@views.route ( ' /start/ ' , methods = [ ' GET ' , ' POST ' ] )
def start ( ) :
2021-12-05 03:49:31 +00:00
from main import db
2021-11-24 17:17:56 +00:00
from . forms import StartQuiz
form = StartQuiz ( )
2021-11-25 23:12:20 +00:00
if request . method == ' GET ' :
2021-11-30 18:06:24 +00:00
_id = session . get ( ' _id ' )
if _id and db . entries . find_one ( { ' _id ' : _id } ) :
return redirect ( url_for ( ' quiz_views.start_quiz ' ) )
2021-11-25 23:12:20 +00:00
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 ' )
2021-12-06 21:47:02 +00:00
test_code = request . form . get ( ' test_code ' ) . replace ( ' — ' , ' ' ) . upper ( )
2021-11-25 23:12:20 +00:00
user_code = request . form . get ( ' user_code ' )
2021-12-04 17:14:28 +00:00
user_code = None if user_code == ' ' else user_code . upper ( )
2021-11-30 18:16:52 +00:00
test = db . tests . find_one ( { ' test_code ' : test_code } )
if not test :
2021-11-25 23:12:20 +00:00
return jsonify ( { ' error ' : ' The exam code you entered is invalid. ' } ) , 400
2021-12-04 17:14:28 +00:00
if user_code and user_code not in test [ ' time_adjustments ' ] :
return jsonify ( { ' error ' : f ' The user code you entered is not valid. ' } ) , 400
2021-12-07 00:08:01 +00:00
if test [ ' expiry_date ' ] < datetime . utcnow ( ) :
2021-12-01 08:26:08 +00:00
return jsonify ( { ' error ' : f ' The exam code you entered expired on { test [ " expiry_date " ] . strftime ( " %d % b % Y " ) } UTC. ' } ) , 400
2021-11-30 18:16:52 +00:00
if test [ ' start_date ' ] > datetime . utcnow ( ) :
2021-12-01 08:26:08 +00:00
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 " ) } UTC. ' } ) , 400
2021-11-28 17:28:14 +00:00
entry = {
2021-11-25 23:12:20 +00:00
' _id ' : uuid4 ( ) . hex ,
' name ' : encrypt ( name ) ,
' email ' : encrypt ( email ) ,
' club ' : encrypt ( club ) ,
2021-11-28 18:17:50 +00:00
' test_code ' : test_code ,
2021-11-30 18:06:24 +00:00
' user_code ' : user_code
2021-11-25 23:12:20 +00:00
}
2021-12-06 13:42:26 +00:00
if db . entries . insert_one ( entry ) :
2021-11-30 18:06:24 +00:00
session [ ' _id ' ] = entry [ ' _id ' ]
2021-11-28 18:17:50 +00:00
return jsonify ( {
2021-11-30 03:11:28 +00:00
' success ' : ' Received and validated test and/or user code. Redirecting to test client. ' ,
2021-11-28 18:17:50 +00:00
' _id ' : entry [ ' _id ' ]
} ) , 200
2021-11-25 23:12:20 +00:00
else :
errors = [ * form . errors ]
return jsonify ( { ' error ' : errors } ) , 400
2021-11-30 03:11:28 +00:00
@views.route ( ' /api/questions/ ' , methods = [ ' POST ' ] )
def fetch_questions ( ) :
2021-12-05 03:49:31 +00:00
from main import app , db
2021-11-30 03:11:28 +00:00
_id = request . get_json ( ) [ ' _id ' ]
2021-11-30 18:06:24 +00:00
entry = db . entries . find_one ( { ' _id ' : _id } )
2021-11-28 18:17:50 +00:00
if not entry :
2021-12-01 00:46:21 +00:00
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
2021-11-28 18:17:50 +00:00
test_code = entry [ ' test_code ' ]
2021-12-04 17:14:28 +00:00
user_code = entry [ ' user_code ' ]
2021-11-28 18:17:50 +00:00
test = db . tests . find_one ( { ' test_code ' : test_code } )
2021-11-30 18:06:24 +00:00
time_limit = test [ ' time_limit ' ]
2021-12-04 17:14:28 +00:00
time_adjustment = 0
2021-11-30 18:06:24 +00:00
if time_limit :
_time_limit = int ( time_limit )
2021-12-04 17:14:28 +00:00
if user_code :
time_adjustment = test [ ' time_adjustments ' ] [ user_code ]
_time_limit + = time_adjustment
2021-11-30 18:06:24 +00:00
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 )
2021-12-04 12:20:03 +00:00
db . tests . find_one_and_update ( { ' _id ' : test [ ' _id ' ] } , { ' $push ' : { ' entries ' : _id } } )
2021-11-28 18:17:50 +00:00
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 ( {
2021-11-30 18:06:24 +00:00
' time_limit ' : end_time ,
' questions ' : questions ,
2021-12-04 17:14:28 +00:00
' start_time ' : entry [ ' start_time ' ] ,
' time_adjustment ' : time_adjustment
2021-11-28 18:17:50 +00:00
} )
2021-11-30 03:11:28 +00:00
@views.route ( ' /test/ ' )
def start_quiz ( ) :
2021-12-05 03:49:31 +00:00
from main import db
2021-11-30 03:11:28 +00:00
_id = session . get ( ' _id ' )
if not _id or not db . entries . find_one ( { ' _id ' : _id } ) :
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 ' )
2021-11-28 18:17:50 +00:00
2021-11-30 18:06:24 +00:00
@views.route ( ' /api/submit/ ' , methods = [ ' POST ' ] )
def submit_quiz ( ) :
2021-12-05 03:49:31 +00:00
from main import app , db
2021-11-30 18:06:24 +00:00
_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 '
2021-12-07 15:52:58 +00:00
if ' end_time ' in entry :
if entry [ ' end_time ' ] :
if datetime . utcnow ( ) > entry [ ' end_time ' ] + timedelta ( minutes = 2 ) :
status = ' late '
2021-11-30 18:06:24 +00:00
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 ( ) :
2021-12-05 03:49:31 +00:00
from main import db , mail
2021-11-30 18:06:24 +00:00
_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 ( ) }
2021-12-01 00:46:21 +00:00
sorted_low_tags = sorted ( tags_low . items ( ) , key = lambda x : x [ 1 ] , reverse = True )
tag_output = [ tag [ 0 ] for tag in sorted_low_tags [ 0 : 3 ] if tag [ 1 ] > 3 ]
if entry [ ' results ' ] [ ' grade ' ] == ' pass ' :
flavour_text_plain = """ Well done on successfully completing the refereeing theory exam. We really appreciate members of our community taking the time to get qualified to referee.
"""
elif entry [ ' results ' ] [ ' grade ' ] == ' merit ' :
flavour_text_plain = """ Congratulations on achieving a merit in the refereeing exam. We are delighted that members of our community work so hard with refereeing.
"""
elif entry [ ' results ' ] [ ' grade ' ] == ' fail ' :
flavour_text_plain = """ Unfortunately, you were not successful in passing the theory exam in this attempt. We hope that this does not dissuade you, and that you try again in the future.
"""
2021-12-04 12:20:03 +00:00
if not entry [ ' status ' ] == ' late ' :
email = Message (
subject = " SKA Refereeing Theory Exam Results " ,
recipients = [ entry [ ' email ' ] ] ,
body = f """ SKA Refereeing Theory Exam \n \n
Candidate Results \n \n
Dear { entry [ ' name ' ] [ ' first_name ' ] } , \n \n
This email is to confirm that you have took the SKA Refereeing Theory Exam . Your test has been evaluated and your results have been generated . \n \n
{ entry [ ' name ' ] [ ' surname ' ] } , { entry [ ' name ' ] [ ' first_name ' ] } \n \n
Email Address : { entry [ ' email ' ] } \n
{ f " Club: { entry [ ' club ' ] } " if entry [ ' club ' ] else ' ' } \n
Date of Test : { entry [ ' submission_time ' ] . strftime ( ' %d % b % Y ' ) } \n
Score : { score } % \n
Grade : { entry [ ' results ' ] [ ' grade ' ] } \n \n
{ flavour_text_plain } \n \n
Based on your answers , we would also suggest you brush up on the following topics as you continue refereeing : \n \n
{ ' , ' . join ( tag_output ) } \n \n
Thank you for taking the time to get qualified as a referee . \n \n
Best wishes , \n
SKA Refereeing
""" ,
html = f """ <h1>SKA Refereeing Theory Exam</h1>
< h2 > Candidate Results < / h2 >
< p > Dear { entry [ ' name ' ] [ ' first_name ' ] } , < / p >
< p > This email is to confirm that you have took the SKA Refereeing Theory Exam . Your test has been evaluated and your results have been generated . < / p >
< h3 > { entry [ ' name ' ] [ ' surname ' ] } , { entry [ ' name ' ] [ ' first_name ' ] } < / h3 >
< p > < strong > Email Address < / strong > : { entry [ ' email ' ] } < / p >
{ f " <p><strong>Club</strong>: { entry [ ' club ' ] } </p> " if entry [ ' club ' ] else ' ' }
< p > < strong > Date of Test < / strong > : { entry [ ' submission_time ' ] . strftime ( ' %d % b % Y ' ) } < / p >
2021-12-06 21:47:02 +00:00
< h1 > { score } % < / h1 >
2021-12-04 12:20:03 +00:00
< h2 > { entry [ ' results ' ] [ ' grade ' ] } < / h2 >
< p > { flavour_text_plain } < / p >
< p > Based on your answers , we would also suggest you revise the following topics as you continue refereeing : < / p >
< ul >
< li > { ' </li><li> ' . join ( tag_output ) } < / li >
< / ul >
< p > Thank you for taking the time to get qualified as a referee . < / p >
< p > Best wishes , < br / >
SKA Refereeing < / p >
""" ,
)
mail . send ( email )
2021-12-01 00:46:21 +00:00
return render_template ( ' /quiz/result.html ' , entry = entry , score = score , tag_output = tag_output )
2021-11-23 13:00:03 +00:00
@views.route ( ' /privacy/ ' )
def privacy ( ) :
2021-11-25 23:12:20 +00:00
return render_template ( ' /quiz/privacy.html ' )