diff --git a/ref-test/app/api/views.py b/ref-test/app/api/views.py
index 546a329..d806562 100644
--- a/ref-test/app/api/views.py
+++ b/ref-test/app/api/views.py
@@ -49,8 +49,8 @@ def _submit_quiz():
id = request.get_json()['id']
answers = request.get_json()['answers']
entry = Entry.query.filter_by(id=id).first()
- if not entry: return jsonify({'error': 'Unrecognised ID.'}), 400
- test = entry['test']
+ if not entry: return jsonify({'error': 'Unrecognised Entry.'}), 400
+ test = entry.test
dataset = test.dataset
success, message = dataset.check_file()
if not success: return jsonify({'error': message}), 500
diff --git a/ref-test/app/forms/quiz.py b/ref-test/app/forms/quiz.py
index e69de29..bda67bd 100644
--- a/ref-test/app/forms/quiz.py
+++ b/ref-test/app/forms/quiz.py
@@ -0,0 +1,11 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField
+from wtforms.validators import InputRequired, Length, Email, Optional
+
+class StartQuiz(FlaskForm):
+ first_name = StringField('First Name(s)', validators=[InputRequired(), Length(max=30)])
+ surname = StringField('Surname', validators=[InputRequired(), Length(max=30)])
+ email = StringField('Email Address', validators=[InputRequired(), Email(message='You must enter a valid email address.'), Length(max=50)])
+ club = StringField('Affiliated Club (Optional)', validators=[Optional(), Length(max=50)])
+ test_code = StringField('Exam Code', validators=[InputRequired(), Length(min=14, max=14)])
+ user_code = StringField('User Code (Optional)', validators=[Optional(), Length(min=6, max=6)])
\ No newline at end of file
diff --git a/ref-test/app/models/entry.py b/ref-test/app/models/entry.py
index 5a9132c..80b9d55 100644
--- a/ref-test/app/models/entry.py
+++ b/ref-test/app/models/entry.py
@@ -67,10 +67,12 @@ class Entry(db.Model):
def get_club(self): return decrypt(self.club)
def start(self):
+ self.generate_id()
self.start_time = datetime.now()
self.status = 'started'
write('tests.log', f'New test started by {self.get_first_name()} {self.get_surname()}.')
db.session.commit()
+ return True, f'New test started with id {self.id}.'
def complete(self, answers:dict=None, result:dict=None):
self.end_time = datetime.now()
@@ -84,6 +86,7 @@ class Entry(db.Model):
self.status = 'late'
self.valid = False
db.session.commit()
+ return True, f'Test entry completed for id {self.id}.'
def validate(self):
if self.valid: return False, f'The entry is already valid.'
diff --git a/ref-test/app/quiz/static/js/script.js b/ref-test/app/quiz/static/js/script.js
index 468866d..e5f0ed1 100644
--- a/ref-test/app/quiz/static/js/script.js
+++ b/ref-test/app/quiz/static/js/script.js
@@ -25,7 +25,7 @@ $('form[name=form-quiz-start]').submit(function(event) {
success: function(response) {
var id = response.id
window.localStorage.setItem('id', id);
- window.location.href = `/test/`;
+ window.location.href = `/quiz/`;
},
error: function(response) {
error_response(response);
diff --git a/ref-test/app/quiz/templates/quiz/result.html b/ref-test/app/quiz/templates/quiz/result.html
index 2603d6b..b358070 100644
--- a/ref-test/app/quiz/templates/quiz/result.html
+++ b/ref-test/app/quiz/templates/quiz/result.html
@@ -6,13 +6,13 @@
Candidate Results
- {{ entry.name.surname }}, {{ entry.name.first_name }}
+ {{ entry.get_surname() }}, {{ entry.get_first_name() }}
- Email Address: {{ entry.email }}
+ Email Address: {{ entry.get_email() }}
{% if entry.club %}
- Club: {{ entry.club }}
+ Club: {{ entry.get_club() }}
{% endif%}
{% if entry.status == 'late' %}
diff --git a/ref-test/app/quiz/templates/quiz/start-quiz.html b/ref-test/app/quiz/templates/quiz/start_quiz.html
similarity index 100%
rename from ref-test/app/quiz/templates/quiz/start-quiz.html
rename to ref-test/app/quiz/templates/quiz/start_quiz.html
diff --git a/ref-test/app/quiz/views.py b/ref-test/app/quiz/views.py
index be97555..86d8b4c 100644
--- a/ref-test/app/quiz/views.py
+++ b/ref-test/app/quiz/views.py
@@ -1,4 +1,11 @@
-from flask import Blueprint
+from ..forms.quiz import StartQuiz
+from ..models import Entry, Test
+from ..tools.test import redirect_if_started
+
+from flask import abort, Blueprint, jsonify, redirect, render_template, request, session
+from flask.helpers import flash, url_for
+
+from datetime import datetime
quiz = Blueprint(
name='quiz',
@@ -9,21 +16,82 @@ quiz = Blueprint(
@quiz.route('/')
@quiz.route('/home/')
+@redirect_if_started
def _home():
- return 'Quiz Home Page'
+ return render_template('/quiz/index.html')
@quiz.route('/instructions/')
def _instructions():
- return 'Instructions'
+ return render_template('/quiz/instructions.html')
@quiz.route('/start/')
def _start():
- return 'Start Quiz'
+ form = StartQuiz()
+ if request.method == 'POST':
+ if form.validate_on_submit():
+ entry = Entry()
+ entry.set_first_name(request.form.get('first_name'))
+ entry.set_surname(request.form.get('surname'))
+ entry.set_club(request.form.get('club'))
+ code = request.form.get('test_code').replace('—', '').lower()
+ test = Test.query.filter_by(code=code)
+ entry.test = test
+ entry.user_code = request.form.get('user_code')
+ entry.user_code = None if entry.user_code == '' else entry.user_code.lower()
+ if not test: return jsonify({'error': 'The exam code you entered is invalid.'}), 400
+ if entry.user_code and entry.user_code not in test.adjustments: return jsonify({'error': f'The user code you entered is not valid.'}), 400
+ if test.end_date < datetime.now(): return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y %H:%M")}.'}), 400
+ if test.start_date > datetime.now(): 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
+ success, message = entry.start()
+ if success:
+ session['id'] = entry.id
+ return jsonify({
+ 'success': 'Received and validated test and/or user code. Redirecting to test client.',
+ 'id': entry.id
+ }), 200
+ return jsonify({'error': 'There was an error processing the user test and/or user codes.'}), 400
+ errors = [*form.test_code.errors, *form.user_code.errors, *form.first_name.errors, *form.surname.errors, *form.email.errors, *form.club.errors]
+ return jsonify({ 'error': errors}), 400
+ render_template('/quiz/start_quiz.html', form = form)
@quiz.route('/quiz/')
def _quiz():
- return 'Quiz Console'
+ id = session.get('id')
+ if not id or not Entry.query.filter_by(id=id).first():
+ flash('Your session was not recognised. Please sign in to the quiz again.', 'error')
+ session.pop('id')
+ return redirect(url_for('quiz_views.start'))
+ return render_template('/quiz/client.html')
@quiz.route('/result/')
def _result():
- return 'Quiz Result'
\ No newline at end of file
+ id = session.get('id')
+ entry = Entry.query.filter_by('id').first()
+ 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_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]
+ revision_plain = ''
+ revision_html = ''
+ 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.
+ """
+ revision_plain = f"""Based on your answers, we would also suggest you brush up on the following topics for your next attempt:\n\n
+ {','.join(tag_output)}\n\n
+ """
+ revision_html = f"""Based on your answers, we would also suggest you brush up on the following topics for your next attempt:
+
+ - {'
- '.join(tag_output)}
+
+ """
+ if not entry['status'] == 'late':
+ pass
+ return render_template('/quiz/result.html', entry=entry, score=score, tag_output=tag_output)
\ No newline at end of file
diff --git a/ref-test/app/tools/test.py b/ref-test/app/tools/test.py
index 469d445..6981f6d 100644
--- a/ref-test/app/tools/test.py
+++ b/ref-test/app/tools/test.py
@@ -1,4 +1,10 @@
from .data import randomise_list
+from ..models import Entry
+
+from flask import redirect, request, session
+from flask.helpers import url_for
+
+from functools import wraps
def parse_test_code(code):
return code.replace('—', '').lower()
@@ -101,4 +107,13 @@ def get_correct_answers(dataset:list):
if block['type'] == 'block':
for question in block['questions']:
output[str(question['q_no'])] = question['options'][question['correct']]
- return output
\ No newline at end of file
+ return output
+
+def redirect_if_started(function):
+ @wraps(function)
+ def wrapper(*args, **kwargs):
+ id = session.get('id')
+ if request.method == 'GET' and id and Entry.query.filter_by(id=id).first():
+ return redirect(url_for('quiz._quiz'))
+ return function(*args, **kwargs)
+ return wrapper