diff --git a/.gitignore b/.gitignore index 5865410..ae5f512 100644 --- a/.gitignore +++ b/.gitignore @@ -146,4 +146,7 @@ out/ ref-test/testing.py # Ignore Encryption Keyfile -.encryption.key \ No newline at end of file +.encryption.key + +# Ignore Font Binaries +**/fonts/ \ No newline at end of file diff --git a/ref-test/admin/forms.py b/ref-test/admin/forms.py index 89e8d5c..9d5efd6 100644 --- a/ref-test/admin/forms.py +++ b/ref-test/admin/forms.py @@ -1,6 +1,7 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField -from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional +from wtforms import StringField, PasswordField, BooleanField, DateField, SelectField +from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional, ValidationError +from datetime import date, timedelta class LoginForm(FlaskForm): username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)]) @@ -41,4 +42,14 @@ class UpdateAccountForm(FlaskForm): password_confirm = PasswordField('Current Password', validators=[InputRequired(), Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')]) email = StringField('Email Address', validators=[Optional(), Email(message='You must enter a valid email address.'), Length(max=50)]) password = PasswordField('Change Password', validators=[Optional(),Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')]) - password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')]) \ No newline at end of file + password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')]) + +class CreateTest(FlaskForm): + time_options = [ + ('none', 'None'), + ('60', '1 hour'), + ('90', '1 hour 30 minutes'), + ('120', '2 hours') + ] + expiry = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) ) + time_limit = SelectField('Time Limit', choices=time_options) \ No newline at end of file diff --git a/ref-test/admin/models.py b/ref-test/admin/models.py new file mode 100644 index 0000000..19c07a1 --- /dev/null +++ b/ref-test/admin/models.py @@ -0,0 +1,45 @@ +import secrets +from datetime import datetime +from uuid import uuid4 +from flask import flash, jsonify + +from main import db +from security import encrypt, decrypt + +class Test: + def __init__(self, _id=None, expiry=None, time_limit=None, creator=None): + self._id = _id + self.expiry = expiry + self.time_limit = None if time_limit == 'none' or time_limit == '' else time_limit + self.creator = creator + + def create(self): + test = { + '_id': self._id, + 'date': datetime.today(), + 'expiry': self.expiry, + 'time_limit': self.time_limit, + 'creator': encrypt(self.creator), + 'test_code': secrets.token_hex(6).upper() + } + if db.tests.insert_one(test): + flash(f'Created a new exam with Exam Code {self.render_test_code(test["test_code"])}.', 'success') + return jsonify({'success': test}), 200 + return jsonify({'error': f'Could not create exam. An error occurred.'}), 400 + + def add_user_code(self, user_code, time_adjustment): + code = { + '_id': uuid4().hex, + 'user_code': user_code, + 'time_adjustment': time_adjustment + } + if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': code}},upsert=False): + return jsonify({'success': code}) + else: + return jsonify({'error': 'An error occurred.'}), 400 + + def render_test_code(self, test_code): + return '—'.join([test_code[:4], test_code[4:8], test_code[8:]]) + + def parse_test_code(self, test_code): + return test_code.replace('—', '') \ No newline at end of file diff --git a/ref-test/admin/static/css/style.css b/ref-test/admin/static/css/style.css index c409e0c..736d9c2 100644 --- a/ref-test/admin/static/css/style.css +++ b/ref-test/admin/static/css/style.css @@ -27,7 +27,7 @@ body { margin: auto; } -.form-signin-heading { +.form-heading { margin-bottom: 2rem; } @@ -65,6 +65,10 @@ body { border-bottom: 2px solid #585858; } +.form-label-group input:active, .form-label-group input:focus { + background-color: transparent; +} + .form-label-group input::-webkit-input-placeholder { color: transparent; } @@ -149,6 +153,50 @@ table.dataTable { width: 100%; } +.alert-db-empty { + width: 100%; + max-width: 720px; + font-size: 14pt; + margin: 20px auto; +} + +.form-date-input, .form-select-input { + position: relative; + margin: 2rem 0; +} + +.form-date-input input, +.form-date-input label, .form-select-input select, .form-select-input label { + padding: var(--input-padding-y) var(--input-padding-x); + font-size: 16pt; + width: 100%; + background-color: transparent; + border: none; + border-bottom: 2px solid #585858; +} + +.datepicker::-webkit-calendar-picker-indicator { + border: 1px; + border-color: gray; + border-radius: 10%; +} + +.form-date-input label, .form-select-input label { + /* position: absolute; */ + /* top: 0; + left: 0; */ + display: block; + width: 100%; + margin-bottom: 0; /* Override default `` margin */ + line-height: 1.5; + color: #495057; + cursor: text; /* Match the input under the label */ + border: 1px solid transparent; + border-radius: .25rem; + transition: all .1s ease-in-out; + z-index: -1; +} + /* Fallback for Edge -------------------------------------------------- */ @supports (-ms-ime-align: auto) { diff --git a/ref-test/admin/static/js/script.js b/ref-test/admin/static/js/script.js index f5fe961..f66be39 100644 --- a/ref-test/admin/static/js/script.js +++ b/ref-test/admin/static/js/script.js @@ -350,6 +350,49 @@ $('form[name=form-update-account]').submit(function(event) { event.preventDefault(); }); +$('form[name=form-create-test]').submit(function(event) { + + var $form = $(this); + var alert = document.getElementById('alert-box'); + var data = $form.serialize(); + console.log(data) + alert.innerHTML = '' + + $.ajax({ + url: window.location.pathname, + type: 'POST', + data: data, + dataType: 'json', + success: function(response) { + window.location.reload(); + }, + error: function(response) { + console.log(response.responseJSON) + if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) { + alert.innerHTML = alert.innerHTML + ` + + + ${response.responseJSON.error} + + + `; + } else if (response.responseJSON.error instanceof Array) { + for (var i = 0; i < response.responseJSON.error.length; i ++) { + alert.innerHTML = alert.innerHTML + ` + + + ${response.responseJSON.error[i]} + + + `; + } + } + } + }); + + event.preventDefault(); +}); + // Dismiss Cookie Alert $('#dismiss-cookie-alert').click(function(event){ diff --git a/ref-test/admin/templates/admin/auth/login.html b/ref-test/admin/templates/admin/auth/login.html index 50906d2..c78eb66 100644 --- a/ref-test/admin/templates/admin/auth/login.html +++ b/ref-test/admin/templates/admin/auth/login.html @@ -4,7 +4,7 @@ {% include "admin/components/server-alerts.html" %} - Log In + Log In {{ form.hidden_tag() }} {{ form.username(class_="form-control", autofocus=true, placeholder="Enter Username") }} diff --git a/ref-test/admin/templates/admin/components/base.html b/ref-test/admin/templates/admin/components/base.html index a8d2574..1e7ca4c 100644 --- a/ref-test/admin/templates/admin/components/base.html +++ b/ref-test/admin/templates/admin/components/base.html @@ -42,9 +42,6 @@ integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"> - + + + + + +{% endblock %} +{% block datatable_scripts %} + + + + + + + + + + + + + + + + {% block custom_data_script %}{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/ref-test/admin/templates/admin/components/server-alerts.html b/ref-test/admin/templates/admin/components/server-alerts.html index 99643d3..529c944 100644 --- a/ref-test/admin/templates/admin/components/server-alerts.html +++ b/ref-test/admin/templates/admin/components/server-alerts.html @@ -4,26 +4,26 @@ {% for category, message in messages %} {% if category == "error" %} - + {{ message|safe }} {% elif category == "success" %} - + {{ message|safe }} {% elif category == "warning" %} - + {{ message|safe }} {% elif category == "cookie_alert" %} {% if not cookie_flash_flag.value %} - + {{ message|safe }} Accept diff --git a/ref-test/admin/templates/admin/settings/users.html b/ref-test/admin/templates/admin/settings/users.html index 600067e..14efcfe 100644 --- a/ref-test/admin/templates/admin/settings/users.html +++ b/ref-test/admin/templates/admin/settings/users.html @@ -1,13 +1,5 @@ -{% extends "admin/components/base.html" %} +{% extends "admin/components/datatable.html" %} {% block title %} SKA Referee Test | Manage Users {% endblock %} -{% block datatable_css %} - - - - - - -{% endblock %} {% block content %} Manage Users @@ -120,22 +112,7 @@ {% endblock %} -{% block datatable_scripts %} - - - - - - - - - - - - - - - +{% block custom_data_script %} + {% endblock %} + {% else %} + + + No exams have been created. Use the form below to create an exam. + + {% endif %} + + + + Create Exam + {{ form.hidden_tag() }} + + {{ form.expiry(placeholder="Enter Expiry Date", class_ = "datepicker") }} + {{ form.expiry.label }} + + + {{ form.time_limit(placeholder="Select Time Limit") }} + {{ form.time_limit.label }} + + {% include "admin/components/client-alerts.html" %} + + + + + + + + + Create Exam + + + + + + +{% endblock %} \ No newline at end of file diff --git a/ref-test/admin/views.py b/ref-test/admin/views.py index dbc70b0..f2e998f 100644 --- a/ref-test/admin/views.py +++ b/ref-test/admin/views.py @@ -10,25 +10,8 @@ from main import db from uuid import uuid4 import secrets from main import mail -from datetime import datetime, timedelta - -from .forms import CreateUserForm - -cookie_consent = Blueprint( - 'cookie_consent', - __name__ -) -@cookie_consent.route('/') -def _cookies(): - resp = redirect('/') - resp.set_cookie( - key = 'cookie_consent', - value = 'True', - max_age = timedelta(days=14) if request.cookies.get('remember') == 'True' else 'Session', - path = '/', - expires = datetime.utcnow() + timedelta(days=14) if request.cookies.get('remember') else 'Session' - ) - return resp +from datetime import datetime, date, timedelta +from .models import Test views = Blueprint( 'admin_views', @@ -99,6 +82,7 @@ def settings(): @admin_account_required @login_required def users(): + from .forms import CreateUserForm form = CreateUserForm() if request.method == 'GET': users_list = decrypt_find(db.users, {}) @@ -259,8 +243,32 @@ def questions(): def upload_questions(): return render_template('/admin/settings/upload-questions.html') -@views.route('/tests/') +@views.route('/tests/', methods=['GET','POST']) @admin_account_required @login_required def tests(): - return render_template('/admin/tests.html') \ No newline at end of file + from .forms import CreateTest + form = CreateTest() + form.time_limit.default='none' + form.process() + if request.method == 'GET': + tests = decrypt_find(db.tests, {}) + return render_template('/admin/tests.html', tests = tests, form = form) + if request.method == 'POST': + if form.validate_on_submit(): + expiry = request.form.get('expiry') + if datetime.strptime(expiry, '%Y-%m-%d').date() < date.today(): + return jsonify({'error': 'The expiry date cannot be in the past.'}), 400 + creator_id = get_id_from_cookie() + creator = decrypt_find_one(db.users, { '_id': creator_id } )['username'] + test = Test( + _id = uuid4().hex, + expiry = expiry, + time_limit = request.form.get('time_limit'), + creator = creator + ) + test.create() + return jsonify({'success': 'New exam created.'}), 200 + else: + errors = [*form.expiry.errors, *form.time_limit.errors] + return jsonify({ 'error': errors}), 400 \ No newline at end of file diff --git a/ref-test/common/__init__.py b/ref-test/common/__init__.py new file mode 100644 index 0000000..d15e272 --- /dev/null +++ b/ref-test/common/__init__.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from flask import Blueprint, redirect, request + +cookie_consent = Blueprint( + 'cookie_consent', + __name__ +) +@cookie_consent.route('/') +def _cookies(): + resp = redirect('/') + resp.set_cookie( + key = 'cookie_consent', + value = 'True', + max_age = timedelta(days=14) if request.cookies.get('remember') == 'True' else 'Session', + path = '/', + expires = datetime.utcnow() + timedelta(days=14) if request.cookies.get('remember') else 'Session' + ) + return resp \ No newline at end of file diff --git a/ref-test/main.py b/ref-test/main.py index e365962..2a3173c 100644 --- a/ref-test/main.py +++ b/ref-test/main.py @@ -37,7 +37,9 @@ if __name__ == '__main__': if not check_keyfile_exists(): generate_keyfile() - from admin.views import views as admin_views, cookie_consent + from common import cookie_consent + + from admin.views import views as admin_views from admin.auth import auth as admin_auth from admin.results import results from quiz.views import views as quiz_views diff --git a/ref-test/quiz/auth.py b/ref-test/quiz/auth.py index be2524e..3633fd7 100644 --- a/ref-test/quiz/auth.py +++ b/ref-test/quiz/auth.py @@ -3,6 +3,4 @@ from flask import Blueprint auth = Blueprint( 'quiz_auth', __name__, - template_folder='templates', - static_folder='static' ) \ No newline at end of file diff --git a/ref-test/quiz/forms.py b/ref-test/quiz/forms.py index 6e848de..c0f508f 100644 --- a/ref-test/quiz/forms.py +++ b/ref-test/quiz/forms.py @@ -1,9 +1,11 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField -from wtforms.validators import InputRequired, Email, Length +from wtforms.validators import InputRequired, Email, Length, Optional class StartQuiz(FlaskForm): - given_name = StringField('Given Name', validators=[InputRequired(), Length(max=15)]) - surname = StringField('Surname', validators=[InputRequired(), Length(max=15)]) + 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', validators=[InputRequired(), Length(max=50)]) \ No newline at end of file + club = StringField('Affiliated Club (Optional)', validators=[Optional(), Length(max=50)]) + auth_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/quiz/static/css/style.css b/ref-test/quiz/static/css/style.css index e69de29..8d737af 100644 --- a/ref-test/quiz/static/css/style.css +++ b/ref-test/quiz/static/css/style.css @@ -0,0 +1,190 @@ +.bg-light { + background-color: #EBE3E1!important; +} + +body { + padding: 80px 0; + line-height: 1.5; + font-size: 14pt; +} + +.site-footer { + background-color: lightgray; + font-size: small; +} + +.site-footer p { + margin: 0; +} + +.quiz-container { + max-width: 720px; +} + +.form-container { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding-top: 40px; + padding-bottom: 40px; +} + +.form-quiz-start { + width: 100%; + max-width: 420px; + padding: 15px; + margin: auto; +} + +.form-heading { + margin-bottom: 2rem; +} + +.form-label-group { + position: relative; + margin-bottom: 2rem; +} + +.form-label-group input, +.form-label-group label { + padding: var(--input-padding-y) var(--input-padding-x); + font-size: 16pt; +} + +.form-label-group label { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + margin-bottom: 0; /* Override default `` margin */ + line-height: 1.5; + color: #495057; + cursor: text; /* Match the input under the label */ + border: 1px solid transparent; + border-radius: .25rem; + transition: all .1s ease-in-out; + z-index: -1; +} + +.form-label-group input { + background-color: transparent; + border: none; + border-radius: 0%; + border-bottom: 2px solid #585858; +} + +.form-label-group input:active, .form-label-group input:focus { + background-color: transparent; +} + +.form-label-group input::-webkit-input-placeholder { + color: transparent; +} + +.form-label-group input:-ms-input-placeholder { + color: transparent; +} + +.form-label-group input::-ms-input-placeholder { + color: transparent; +} + +.form-label-group input::-moz-placeholder { + color: transparent; +} + +.form-label-group input::placeholder { + color: transparent; +} + +.form-label-group input:not(:placeholder-shown) { + padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3)); + padding-bottom: calc(var(--input-padding-y) / 3); +} + +.form-label-group input:not(:placeholder-shown) ~ label { + padding-top: calc(var(--input-padding-y) / 3); + padding-bottom: calc(var(--input-padding-y) / 3); + font-size: 12px; + color: #777; +} + +.form-check { + margin-bottom: 2rem; +} + +.checkbox input { + transform: scale(1.5); + margin-right: 1rem; +} + +.signin-forgot-password { + font-size: 14pt; +} + +.form-submission-button { + margin-bottom: 2rem; +} + +.form-submission-button button, .form-submission-button a { + margin: 1rem; + vertical-align: middle; +} + +.form-submission-button button span, .form-submission-button button svg, .form-submission-button a span, .form-submission-button a svg { + margin: 0 2px; +} + +/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on November 23, 2021 */ + +@font-face { + font-family: 'opendyslexic3bold'; + src: url('../fonts/opendyslexic3-bold-webfont.woff2') format('woff2'), + url('../fonts/opendyslexic3-bold-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} + +@font-face { + font-family: 'opendyslexic3regular'; + src: url('../fonts/opendyslexic3-regular-webfont.woff2') format('woff2'), + url('../fonts/opendyslexic3-regular-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} + +@font-face { + font-family: 'opendyslexicmonoregular'; + src: url('../fonts/opendyslexicmono-regular-webfont.woff2') format('woff2'), + url('../fonts/opendyslexicmono-regular-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} + +/* Fallback for Edge +-------------------------------------------------- */ +@supports (-ms-ime-align: auto) { + .form-label-group label { + display: none; + } + .form-label-group input::-ms-input-placeholder { + color: #777; + } +} + + /* Fallback for IE + -------------------------------------------------- */ +@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + .form-label-group label { + display: none; + } + .form-label-group input:-ms-input-placeholder { + color: #777; + } +} + \ No newline at end of file diff --git a/ref-test/quiz/static/js/script.js b/ref-test/quiz/static/js/script.js index e69de29..1919828 100644 --- a/ref-test/quiz/static/js/script.js +++ b/ref-test/quiz/static/js/script.js @@ -0,0 +1,13 @@ +$(document).ready(function() { + $("#od-font-test").click(function(){ + $("body").css("font-family", "opendyslexic3regular") + }); + + $('.auth-code-input').keyup(function() { + var input = $(this).val().split("-").join("").split("—").join(""); // remove hyphens and mdashes + if (input.length > 0) { + input = input.match(new RegExp('.{1,4}', 'g')).join("—"); + } + $(this).val(input); + }); +}); \ No newline at end of file diff --git a/ref-test/quiz/templates/quiz/base.html b/ref-test/quiz/templates/quiz/base.html deleted file mode 100644 index 499abe3..0000000 --- a/ref-test/quiz/templates/quiz/base.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {% block title %} Site Title {% endblock %} - - - - - - SKA Refereeing Test - - - - - - - - - - \ No newline at end of file diff --git a/ref-test/quiz/templates/quiz/components/base.html b/ref-test/quiz/templates/quiz/components/base.html new file mode 100644 index 0000000..7c01603 --- /dev/null +++ b/ref-test/quiz/templates/quiz/components/base.html @@ -0,0 +1,59 @@ + + + + + + + + + {% block title %} SKA Referee Test {% endblock %} + + + + {% block navbar %} + {% include "quiz/components/navbar.html" %} + {% endblock %} + + + {% block top_alerts %} + {% include "quiz/components/server-alerts.html" %} + {% endblock %} + {% block content %}{% endblock %} + + + + + + + + + + + + \ No newline at end of file diff --git a/ref-test/quiz/templates/quiz/components/footer.html b/ref-test/quiz/templates/quiz/components/footer.html new file mode 100644 index 0000000..e69de29 diff --git a/ref-test/quiz/templates/quiz/components/navbar.html b/ref-test/quiz/templates/quiz/components/navbar.html new file mode 100644 index 0000000..ae2e10c --- /dev/null +++ b/ref-test/quiz/templates/quiz/components/navbar.html @@ -0,0 +1,5 @@ + + + SKA Refereeing Test + + \ No newline at end of file diff --git a/ref-test/quiz/templates/quiz/components/server-alerts.html b/ref-test/quiz/templates/quiz/components/server-alerts.html new file mode 100644 index 0000000..e69de29 diff --git a/ref-test/quiz/templates/quiz/index.html b/ref-test/quiz/templates/quiz/index.html new file mode 100644 index 0000000..b268914 --- /dev/null +++ b/ref-test/quiz/templates/quiz/index.html @@ -0,0 +1,24 @@ +{% extends "quiz/components/base.html" %} + +{% block content %} + SKA Refereeing Theory Exam + + + This app will allow you to take the Exam on-line. This app should also allow you to adjust the way the quiz is rendered to suit your access needs. This could include using a screen reader, changing the display font size or typeface, or navigating questions and answers via the keyboard. + + + + Instructions + + + + Other Info + + + + When you are ready to begin the quiz, click the following button. + + + Take the Quiz + +{% endblock %} \ No newline at end of file diff --git a/ref-test/quiz/templates/quiz/privacy.html b/ref-test/quiz/templates/quiz/privacy.html new file mode 100644 index 0000000..e69de29 diff --git a/ref-test/quiz/templates/quiz/start-quiz.html b/ref-test/quiz/templates/quiz/start-quiz.html new file mode 100644 index 0000000..e2918db --- /dev/null +++ b/ref-test/quiz/templates/quiz/start-quiz.html @@ -0,0 +1,43 @@ +{% extends "quiz/components/base.html" %} +{% import "bootstrap/wtf.html" as wtf %} + +{% block content %} + + + Take the Exam + {{ form.hidden_tag() }} + + {{ form.first_name(class_="form-control", autofocus=true, placeholder="Enter First Name(s)") }} + {{ form.first_name.label }} + + + {{ form.surname(class_="form-control", placeholder="Enter Surname") }} + {{ form.surname.label }} + + + {{ form.email(class_="form-control", placeholder="Enter Email Address") }} + {{ form.email.label }} + + + {{ form.club(class_="form-control", placeholder="Enter Affiliated Club") }} + {{ form.club.label }} + + + {{ form.auth_code(class_="form-control auth-code-input", placeholder="Enter Exam Code") }} + {{ form.auth_code.label }} + + + {{ form.user_code(class_="form-control", placeholder="Enter User Code") }} + {{ form.user_code.label }} + + {% include "admin/components/client-alerts.html" %} + + + + Start Quiz + + + + + +{% endblock %} \ No newline at end of file diff --git a/ref-test/quiz/views.py b/ref-test/quiz/views.py index 4ee2791..71ee15f 100644 --- a/ref-test/quiz/views.py +++ b/ref-test/quiz/views.py @@ -1,8 +1,9 @@ -from flask import Blueprint +from flask import Blueprint, render_template views = Blueprint( 'quiz_views', __name__, + static_url_path='', template_folder='templates', static_folder='static' ) @@ -10,7 +11,13 @@ views = Blueprint( @views.route('/') @views.route('/home/') def home(): - return f'Ref Test Home Page' + return render_template('/quiz/index.html') + +@views.route('/start/', methods = ['GET', 'POST']) +def start(): + from .forms import StartQuiz + form = StartQuiz() + return render_template('/quiz/start-quiz.html', form=form) @views.route('/privacy/') def privacy(): diff --git a/ref-test/security/database.py b/ref-test/security/database.py index c1a86bd..9adb51e 100644 --- a/ref-test/security/database.py +++ b/ref-test/security/database.py @@ -1,6 +1,6 @@ from pymongo import collection from . import encrypt, decrypt -encrypted_parameters = ['username', 'email', 'name', 'club'] +encrypted_parameters = ['username', 'email', 'name', 'club', 'creator'] def decrypt_find(collection:collection, query:dict): cursor = collection.find({})
+ This app will allow you to take the Exam on-line. This app should also allow you to adjust the way the quiz is rendered to suit your access needs. This could include using a screen reader, changing the display font size or typeface, or navigating questions and answers via the keyboard. +
+ Instructions +
+ Other Info +
+ When you are ready to begin the quiz, click the following button. +