19 Commits

Author SHA1 Message Date
2bed84e60b Merge branch 'development' 2025-10-06 16:15:20 +01:00
25fb3fdda4 Update python version number 2025-10-06 16:14:04 +01:00
fa104d7d1b Updated for before_first_request decorator deprecation 2025-10-06 16:11:57 +01:00
cb747b4832 Removed deprecated cryptography method 2025-10-06 16:11:07 +01:00
845fdcdf8d Updated dependencies 2025-10-06 16:09:57 +01:00
7c4a844101 Merge branch 'development' 2025-10-06 15:15:02 +01:00
54e1653cb5 Fixed typo 2025-10-06 15:14:07 +01:00
2235539314 Merge branch 'development' 2025-10-06 14:52:36 +01:00
716206dc65 Disable randomisation 2025-10-06 14:50:18 +01:00
a8eda4078d Added Barrowland Bears Korfball Club to list of clubs dropdown 2025-10-06 14:50:00 +01:00
502e694a17 Merge branch 'development' 2023-10-20 20:59:35 +01:00
d28cd6daed Updated instructions for the test 2023-10-20 20:58:50 +01:00
58782f6db7 Merge branch 'development' 2023-07-01 21:49:21 +01:00
57b25cd214 Formatted DataTable date to ISO-8601 for sorting 2023-07-01 21:48:36 +01:00
666e12253e Merge branch 'development' 2023-07-01 21:33:04 +01:00
8013a776a9 Merge branch 'development' of ssh://git.vsnt.uk:2222/viveksantayana/ska-referee-test into development 2023-07-01 21:26:24 +01:00
aa1f46ee62 Bugfix: response to invalid exam codes 2023-07-01 21:24:29 +01:00
dbd8d6bbe3 Serve static files for analysis directly 2023-03-07 11:38:04 +00:00
fed46eaa1e Bugfix: stdev exception when only one test 2023-03-07 11:21:41 +00:00
13 changed files with 81 additions and 49 deletions

View File

@@ -17,6 +17,7 @@ services:
- ./ref-test/app/editor/static:/usr/share/nginx/html/editor/static:ro - ./ref-test/app/editor/static:/usr/share/nginx/html/editor/static:ro
- ./ref-test/app/quiz/static:/usr/share/nginx/html/quiz/static:ro - ./ref-test/app/quiz/static:/usr/share/nginx/html/quiz/static:ro
- ./ref-test/app/view/static:/usr/share/nginx/html/view/static:ro - ./ref-test/app/view/static:/usr/share/nginx/html/view/static:ro
- ./ref-test/app/analysis/static:/usr/share/nginx/html/analysis/static:ro
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443

View File

@@ -45,6 +45,11 @@ server {
alias /usr/share/nginx/html/view/static/; alias /usr/share/nginx/html/view/static/;
} }
location ^~ /admin/analysis/static/ {
include /etc/nginx/mime.types;
alias /usr/share/nginx/html/analysis/static/;
}
# Proxy to the main app for all other requests # Proxy to the main app for all other requests
location / { location / {
include /etc/nginx/conf.d/proxy_headers.conf; include /etc/nginx/conf.d/proxy_headers.conf;

View File

@@ -1,4 +1,4 @@
FROM python:3.10-slim FROM python:3.13-slim
ARG DATA=./data/ ARG DATA=./data/
ENV DATA=$DATA ENV DATA=$DATA
WORKDIR /ref-test WORKDIR /ref-test

View File

@@ -61,10 +61,8 @@ def create_app():
app.register_blueprint(view, url_prefix='/admin/view') app.register_blueprint(view, url_prefix='/admin/view')
app.register_blueprint(analysis, url_prefix='/admin/analysis') app.register_blueprint(analysis, url_prefix='/admin/analysis')
"""Create Database Tables before First Request""" """Create Database Tables when creating app"""
@app.before_first_request with app.app_context():
def _create_database_tables(): db.create_all()
with app.app_context():
db.create_all()
return app return app

View File

@@ -54,7 +54,7 @@
</td> </td>
<td> <td>
{% if entry.end_time %} {% if entry.end_time %}
{{ entry.end_time.strftime('%d %b %Y') }} {{ entry.end_time.strftime('%Y-%m-%d %H:%M') }}
{% endif %} {% endif %}
</td> </td>
<td> <td>

View File

@@ -43,7 +43,7 @@
{{ element.get_name() }} {{ element.get_name() }}
</td> </td>
<td> <td>
{{ element.date.strftime('%d %b %Y %H:%M') }} {{ element.date.strftime('%Y-%m-%d %H:%M') }}
</td> </td>
<td> <td>
{{ element.creator.get_username() }} {{ element.creator.get_username() }}

View File

@@ -33,13 +33,13 @@
{% for test in tests %} {% for test in tests %}
<tr class="table-row"> <tr class="table-row">
<td> <td>
{{ test.start_date.strftime('%d %b %y %H:%M') }} {{ test.start_date.strftime('%Y-%m-%d %H:%M') }}
</td> </td>
<td> <td>
{{ test.get_code() }} {{ test.get_code() }}
</td> </td>
<td> <td>
{{ test.end_date.strftime('%d %b %Y %H:%M') }} {{ test.end_date.strftime('%Y-%m-%d %H:%M') }}
</td> </td>
<td> <td>
{% if test.time_limit == None -%} {% if test.time_limit == None -%}

View File

@@ -55,7 +55,11 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text">Standard Deviation</span> <span class="input-group-text">Standard Deviation</span>
<span class="form-control"> <span class="form-control">
{{ analysis.scores.stdev|round(2) }} {% if analysis.scores.stdev %}
{{ analysis.scores.stdev|round(2) }}
{% else %}
{{ None }}
{% endif %}
</span> </span>
</div> </div>
<div class="input-group mb-3"> <div class="input-group mb-3">

View File

@@ -43,7 +43,7 @@ def _fetch_questions():
data_path = dataset.get_file() data_path = dataset.get_file()
with open(data_path, 'r') as data_file: with open(data_path, 'r') as data_file:
data = loads(data_file.read()) data = loads(data_file.read())
questions = generate_questions(data) questions = generate_questions(dataset=data, randomise=False)
return jsonify({ return jsonify({
'time_limit': end_time, 'time_limit': end_time,
'questions': questions, 'questions': questions,

View File

@@ -41,7 +41,7 @@ class User(UserMixin, db.Model):
def set_password(self): raise AttributeError('set_password is not a readable attribute.') def set_password(self): raise AttributeError('set_password is not a readable attribute.')
set_password.setter set_password.setter
def set_password(self, password:str): self.password = generate_password_hash(password, method="sha256") def set_password(self, password:str): self.password = generate_password_hash(password, method="scrypt")
def verify_password(self, password:str): return check_password_hash(self.password, password) def verify_password(self, password:str): return check_password_hash(self.password, password)

View File

@@ -3,13 +3,36 @@
{% block content %} {% block content %}
<div class="instruction-container"> <div class="instruction-container">
<h3>Instructions</h3> <h3>Instructions</h3>
<p>
Thank you for putting yourself forward to sit the SKA Referee Theory Exam. Please read the following instructions carefully.
</p>
<h4>
Taking the Exam
</h4>
<ul> <ul>
<li> <li>
The exam comprises 100 multiple-choice questions. The exam consists of 100 questions, all of them multiple choice with two or three options, which are designed to test your knowledge of a wide range of rules. For each question, answer what decision you would give as a referee unless the question instructs otherwise.
</li> </li>
<li> <li>
For each question, answer what decision you would give as a referee unless the question instructs otherwise. It should take around an hour to complete.
</li> </li>
<li>
The exam should be taken under exam conditions. Materials such as the official rules, guidelines, revision resources, or similar should not be consulted during the test.
</li>
<li>
We would remind candidates that whilst we are relying on your honesty in this test, your theory knowledge will make up a part of the practical assessment when you are observed refereeing a game.
</li>
<li>
You also may not discuss the test with any other person while you are sitting it.
</li>
<li>
If you have any queries before the exam or would like further feedback on the test, your emails are welcome.
</li>
</ul>
<h4>
Using the Web App
</h4>
<ul>
<li> <li>
You will be able to customise the display settings of the exam from the settings panel by clicking on the red gear button <a class="btn btn-danger" aria-title="Settings" title="Settings" onclick="return false;"><i class="bi bi-gear-fill"></i></a>. You will be able to customise the display settings of the exam from the settings panel by clicking on the red gear button <a class="btn btn-danger" aria-title="Settings" title="Settings" onclick="return false;"><i class="bi bi-gear-fill"></i></a>.
</li> </li>
@@ -17,7 +40,7 @@
You can view your progress at a glance, as well as navigate to any question in the quiz, using the question grid, accessed via the yellow grid button <a class="btn btn-warning" aria-title="Question Grid" title="Question Grid" onclick="return false;"><i class="bi bi-table"></i></a>. You can view your progress at a glance, as well as navigate to any question in the quiz, using the question grid, accessed via the yellow grid button <a class="btn btn-warning" aria-title="Question Grid" title="Question Grid" onclick="return false;"><i class="bi bi-table"></i></a>.
</li> </li>
<li> <li>
If you are unsure of the answer to a question or would like to revise a question, you can flag the question to review it later on using the flag button button <a class="btn btn-secondary" id="q-nav-flag" title="Flag Button." onclick="return false;"><i class="bi bi-flag-fill"></i></a>. If you are unsure of the answer to a question or would like to return to a question later, you can flag the question using the flag button button <a class="btn btn-secondary" id="q-nav-flag" title="Flag Button." onclick="return false;"><i class="bi bi-flag-fill"></i></a> to serve as a reminder for you to come back to it later.
</li> </li>
</ul> </ul>
</div> </div>
@@ -46,7 +69,7 @@
Results Results
</h4> </h4>
<p> <p>
The results of your exam will be processed immediately and sent to the SKA Refereeing Coordinator. You will also be emailed a copy of your results. The results of your exam will be processed immediately and sent to the SKA Refereeing Coordinator. You will also be emailed a copy of your results. If you do not receive an email, make sure to check your spam folder.
</p> </p>
<p> <p>
When you are ready to begin the quiz, click the following button. When you are ready to begin the quiz, click the following button.

View File

@@ -30,6 +30,7 @@ def _instructions():
@quiz.route('/start/', methods=['GET', 'POST']) @quiz.route('/start/', methods=['GET', 'POST'])
def _start(): def _start():
clubs = [ clubs = [
'Barrowland Bears Korfball Club',
'Dundee Korfball Club', 'Dundee Korfball Club',
'Edinburgh City Korfball Club', 'Edinburgh City Korfball Club',
'Edinburgh Mavericks Korfball Club', 'Edinburgh Mavericks Korfball Club',
@@ -59,11 +60,11 @@ def _start():
except Exception as exception: except Exception as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not test: return jsonify({'error': 'The exam code you entered is invalid.'}), 400
entry.test = test entry.test = test
entry.dataset = test.dataset entry.dataset = test.dataset
entry.user_code = request.form.get('user_code') entry.user_code = request.form.get('user_code')
entry.user_code = None if entry.user_code == '' else entry.user_code.lower() 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 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.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 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

View File

@@ -1,33 +1,33 @@
blinker==1.5 blinker==1.9.0
cffi==1.15.1 cffi==2.0.0
click==8.1.3 click==8.3.0
cryptography==39.0.2 cryptography==46.0.2
dnspython==2.3.0 dnspython==2.8.0
dominate==2.7.0 dominate==2.9.1
email-validator==1.3.1 email-validator==2.3.0
Flask==2.2.3 Flask==3.1.2
Flask-Bootstrap==3.3.7.1 Flask-Bootstrap==3.3.7.1
Flask-Login==0.6.2 Flask-Login==0.6.3
Flask-Mail==0.9.1 Flask-Mail==0.10.0
Flask-SQLAlchemy==3.0.3 Flask-SQLAlchemy==3.1.1
Flask-WTF==1.1.1 Flask-WTF==1.2.2
greenlet==2.0.2 greenlet==3.2.4
gunicorn==20.1.0 gunicorn==23.0.0
idna==3.4 idna==3.10
itsdangerous==2.1.2 itsdangerous==2.2.0
Jinja2==3.1.2 Jinja2==3.1.6
MarkupSafe==2.1.2 MarkupSafe==3.0.3
pip==23.0.1 packaging==25.0
pycparser==2.21 pycparser==2.23
PyMySQL==1.0.2 PyMySQL==1.1.2
python-dotenv==1.0.0 python-dotenv==1.1.1
setuptools==67.4.0 setuptools==80.9.0
six==1.16.0 six==1.17.0
SQLAlchemy==2.0.4 SQLAlchemy==2.0.43
sqlalchemy-json==0.5.0 sqlalchemy-json==0.7.0
SQLAlchemy-Utils==0.40.0 SQLAlchemy-Utils==0.42.0
typing_extensions==4.5.0 typing_extensions==4.15.0
visitor==0.1.3 visitor==0.1.3
Werkzeug==2.2.3 Werkzeug==3.1.3
wheel==0.38.4 wheel==0.45.1
WTForms==3.0.1 WTForms==3.2.1