12 Commits

21 changed files with 40 additions and 33 deletions

View File

@ -57,6 +57,16 @@ Once in the destination folder, clone all the relevant files you will need for t
(Remember to include the trailing dot at the end, as that indicates to Git to download the files in the current directory.) (Remember to include the trailing dot at the end, as that indicates to Git to download the files in the current directory.)
#### Choose What Database Engine You Will Use
This app is designed to use an SQLite database by default.
You can set it up to use a MySQL database by configuring the environment variables accordingly.
If your database is being hosted remotely, make sure the MySQL database has the proper authentication for the user from a remote server.
Alternatively, you can also use the second `docker-compose-mysql.yml` file which provides a MySQL database as part of the cluster.
To use the second `docker-compose-mysql.yml` file, use the following command at the last step of the installation:
```sudo docker compose -f docker-compose-mysql.yml up```
#### Populate Environment Variables #### Populate Environment Variables
Configuration values for the app are stored in the environment variables file. Configuration values for the app are stored in the environment variables file.

View File

@ -25,7 +25,7 @@ services:
networks: networks:
- frontend - frontend
depends_on: depends_on:
app: - app
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
app: app:

View File

@ -24,7 +24,7 @@ services:
networks: networks:
- frontend - frontend
depends_on: depends_on:
app: - app
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
app: app:
@ -43,7 +43,7 @@ services:
- frontend - frontend
- backend - backend
depends_on: depends_on:
postfix: - postfix
postfix: postfix:
container_name: reftest_postfix container_name: reftest_postfix

View File

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark"> <nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container"> <div class="container">
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a> <a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest | Admin</a>
<button <button
class="navbar-toggler" class="navbar-toggler"
type="button" type="button"

View File

@ -19,7 +19,7 @@
<div class="d-flex w-100 justify-content-between"> <div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Dataset</h5> <h5 class="mb-1">Dataset</h5>
</div> </div>
{{ test.dataset.date.strftime('%Y%m%d%H%M%S') }} <a href="{{ url_for('view._view_console', id=test.dataset.id) }}">{{ test.dataset.get_name() }}</a>
</li> </li>
<li class="list-group-item list-group-item-action"> <li class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between"> <div class="d-flex w-100 justify-content-between">

View File

@ -215,7 +215,7 @@ def _update_user(id:str):
if request.method == 'POST': if request.method == 'POST':
if not user: return jsonify({'error': 'User does not exist.'}), 400 if not user: return jsonify({'error': 'User does not exist.'}), 400
if form.validate_on_submit(): if form.validate_on_submit():
if not user.verify_password(request.form.get('confirm_password')): return jsonify({'error': 'Invalid password for your account.'}), 401 if not current_user.verify_password(request.form.get('confirm_password')): return jsonify({'error': 'Invalid password for your account.'}), 401
success, message = user.update( success, message = user.update(
password = request.form.get('password'), password = request.form.get('password'),
email = request.form.get('email'), email = request.form.get('email'),

View File

@ -37,7 +37,7 @@ class Config(object):
MYSQL_USER = os.getenv('MYSQL_USER') MYSQL_USER = os.getenv('MYSQL_USER')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD') MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD')
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{MYSQL_DATABASE}' SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{MYSQL_DATABASE}'
else: SQLALCHEMY_DATABASE_URI = f'sqlite:///{Path(os.path.abspath(f"{DATA}/database.db"))}' else: SQLALCHEMY_DATABASE_URI = f'sqlite:///{Path(os.path.abspath(f"{DATA}/db.sqlite"))}'
class Production(Config): class Production(Config):
pass pass

View File

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark"> <nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container"> <div class="container">
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a> <a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest | Admin</a>
<button <button
class="navbar-toggler" class="navbar-toggler"
type="button" type="button"

View File

@ -14,8 +14,7 @@ from pathlib import Path
from uuid import uuid4 from uuid import uuid4
class Dataset(db.Model): class Dataset(db.Model):
id = db.Column(db.String(36), index=True, primary_key=True)
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.String(128), nullable=False) name = db.Column(db.String(128), nullable=False)
tests = db.relationship('Test', backref='dataset') tests = db.relationship('Test', backref='dataset')
creator_id = db.Column(db.String(36), db.ForeignKey('user.id')) creator_id = db.Column(db.String(36), db.ForeignKey('user.id'))

View File

@ -11,16 +11,15 @@ from datetime import datetime, timedelta
from uuid import uuid4 from uuid import uuid4
class Entry(db.Model): class Entry(db.Model):
id = db.Column(db.String(36), index=True, primary_key=True)
id = db.Column(db.String(36), primary_key=True)
first_name = db.Column(db.String(128), nullable=False) first_name = db.Column(db.String(128), nullable=False)
surname = db.Column(db.String(128), nullable=False) surname = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=False)
club = db.Column(db.String(128), nullable=True) club = db.Column(db.String(128), nullable=True)
test_id = db.Column(db.String(36), db.ForeignKey('test.id')) test_id = db.Column(db.String(36), db.ForeignKey('test.id'))
user_code = db.Column(db.String(6), nullable=True) user_code = db.Column(db.String(6), nullable=True)
start_time = db.Column(db.DateTime, nullable=True) start_time = db.Column(db.DateTime, index=True, nullable=True)
end_time = db.Column(db.DateTime, nullable=True) end_time = db.Column(db.DateTime, index=True, nullable=True)
status = db.Column(db.String(16), nullable=True) status = db.Column(db.String(16), nullable=True)
valid = db.Column(db.Boolean, default=True, nullable=True) valid = db.Column(db.Boolean, default=True, nullable=True)
answers = db.Column(MutableJson, nullable=True) answers = db.Column(MutableJson, nullable=True)

View File

@ -9,10 +9,9 @@ import secrets
from uuid import uuid4 from uuid import uuid4
class Test(db.Model): class Test(db.Model):
id = db.Column(db.String(36), index=True, primary_key=True)
id = db.Column(db.String(36), primary_key=True) code = db.Column(db.String(36), index=True, nullable=False)
code = db.Column(db.String(36), nullable=False) start_date = db.Column(db.DateTime, index=True, nullable=True)
start_date = db.Column(db.DateTime, nullable=True)
end_date = db.Column(db.DateTime, nullable=True) end_date = db.Column(db.DateTime, nullable=True)
time_limit = db.Column(db.Integer, nullable=True) time_limit = db.Column(db.Integer, nullable=True)
creator_id = db.Column(db.String(36), db.ForeignKey('user.id')) creator_id = db.Column(db.String(36), db.ForeignKey('user.id'))

View File

@ -11,11 +11,11 @@ from werkzeug.security import check_password_hash, generate_password_hash
import secrets import secrets
from uuid import uuid4 from uuid import uuid4
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
id = db.Column(db.String(36), primary_key=True) id = db.Column(db.String(36), index=True, primary_key=True)
username = db.Column(db.String(128), nullable=False) username = db.Column(db.String(128), nullable=False)
password = db.Column(db.String(128), nullable=False) password = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=False)
reset_token = db.Column(db.String(20), nullable=True) reset_token = db.Column(db.String(20), index=True, nullable=True)
verification_token = db.Column(db.String(20), nullable=True) verification_token = db.Column(db.String(20), nullable=True)
tests = db.relationship('Test', backref='creator') tests = db.relationship('Test', backref='creator')
datasets = db.relationship('Dataset', backref='creator') datasets = db.relationship('Dataset', backref='creator')

View File

@ -17,7 +17,7 @@
/> />
{% block style %} {% block style %}
{% endblock %} {% endblock %}
<title>{% block title %} SKA Referee Test Beta {% endblock %}</title> <title>{% block title %} SKA Referee Test {% endblock %}</title>
{% include "quiz/components/og-meta.html" %} {% include "quiz/components/og-meta.html" %}
</head> </head>
<body class="bg-light"> <body class="bg-light">

View File

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav"> <nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav">
<div class="container"> <div class="container">
<p class="navbar-brand mb-0 h1">SKA Refereeing Test (Beta)</p> <p class="navbar-brand mb-0 h1">SKA Refereeing Test</p>
<div class="quiz-console w-100" style="display: none;" id="q-topbar"> <div class="quiz-console w-100" style="display: none;" id="q-topbar">
<div class="d-flex justify-content align-middle"> <div class="d-flex justify-content align-middle">
<div class="container d-flex justify-content-center"> <div class="container d-flex justify-content-center">

View File

@ -18,7 +18,7 @@
<link rel="shortcut icon" href="{{ url_for('views.static', filename='favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('views.static', filename='favicon.ico') }}">
{% block style %} {% block style %}
{% endblock %} {% endblock %}
<title>{% block title %} SKA Referee Test Beta {% endblock %}</title> <title>{% block title %} SKA Referee Test {% endblock %}</title>
</head> </head>
<body class="bg-light"> <body class="bg-light">

View File

@ -17,7 +17,7 @@
/> />
{% block style %} {% block style %}
{% endblock %} {% endblock %}
<title>{% block title %} SKA Referee Test Beta {% endblock %}</title> <title>{% block title %} SKA Referee Test {% endblock %}</title>
{% include "components/og-meta.html" %} {% include "components/og-meta.html" %}
</head> </head>
<body class="bg-light"> <body class="bg-light">

View File

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav"> <nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav">
<div class="container"> <div class="container">
<p class="navbar-brand mb-0 h1">SKA Refereeing Test (Beta)</p> <p class="navbar-brand mb-0 h1">SKA Refereeing Test</p>
<div class="quiz-console w-100" style="display: none;" id="q-topbar"> <div class="quiz-console w-100" style="display: none;" id="q-topbar">
<div class="d-flex justify-content align-middle"> <div class="d-flex justify-content align-middle">
<div class="container d-flex justify-content-center"> <div class="container d-flex justify-content-center">

View File

@ -1,4 +1,3 @@
from .data import load
from ..models import User from ..models import User
from ..tools.logs import write from ..tools.logs import write

View File

@ -1,5 +1,3 @@
from ..extensions import db
from ..tools.logs import write from ..tools.logs import write
from flask import jsonify from flask import jsonify

View File

@ -1,6 +1,6 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark"> <nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container"> <div class="container">
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a> <a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest | Admin</a>
<button <button
class="navbar-toggler" class="navbar-toggler"
type="button" type="button"

View File

@ -1,7 +1,7 @@
blinker==1.5 blinker==1.5
cffi==1.15.1 cffi==1.15.1
click==8.1.3 click==8.1.3
cryptography==37.0.4 cryptography==38.0.1
dnspython==2.2.1 dnspython==2.2.1
dominate==2.7.0 dominate==2.7.0
email-validator==1.2.1 email-validator==1.2.1
@ -11,19 +11,22 @@ Flask-Login==0.6.2
Flask-Mail==0.9.1 Flask-Mail==0.9.1
Flask-SQLAlchemy==2.5.1 Flask-SQLAlchemy==2.5.1
Flask-WTF==1.0.1 Flask-WTF==1.0.1
greenlet==1.1.2 greenlet==1.1.3
gunicorn==20.1.0 gunicorn==20.1.0
idna==3.3 idna==3.3
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.1 MarkupSafe==2.1.1
pip==22.2.2
pycparser==2.21 pycparser==2.21
PyMySQL==1.0.2 PyMySQL==1.0.2
python-dotenv==0.20.0 python-dotenv==0.21.0
setuptools==65.3.0
six==1.16.0 six==1.16.0
SQLAlchemy==1.4.40 SQLAlchemy==1.4.41
sqlalchemy-json==0.5.0 sqlalchemy-json==0.5.0
SQLAlchemy-Utils==0.38.3 SQLAlchemy-Utils==0.38.3
visitor==0.1.3 visitor==0.1.3
Werkzeug==2.2.2 Werkzeug==2.2.2
wheel==0.37.1
WTForms==3.0.1 WTForms==3.0.1