21 Commits

Author SHA1 Message Date
294f1e42f7 Added timezone env variable 2022-08-11 17:31:20 +01:00
070ce19fcc Added instructions on updating 2022-08-11 17:19:05 +01:00
615e59fc6d Updated form error handling 2022-08-11 16:58:00 +01:00
68314a4ed2 Add handling of anonymous user when updating account 2022-08-11 16:28:47 +01:00
b90761fd2c Simplify variabe nale 2022-08-11 16:28:13 +01:00
af03193217 Change user variable name 2022-08-11 16:13:31 +01:00
730a75c44d Bugfix: reset password 2022-08-11 16:05:28 +01:00
70883db5ad Changed dockerignore stricture 2022-08-11 13:09:41 +01:00
7cefb487da Bugfix: reset password 2022-08-11 13:09:34 +01:00
2e1b01ec9b Bugfix: reset password 2022-08-11 13:02:55 +01:00
a7a5a03991 Consistency in paths for templates 2022-08-11 13:02:41 +01:00
b36c6bfd18 Bugfix: reset password 2022-08-11 12:51:17 +01:00
a613b0006b Bugfix: password reset 2022-08-11 12:44:42 +01:00
d4db8692e7 Remove debug line 2022-08-11 12:13:33 +01:00
37ad36da31 Add debug for email reset 2022-08-11 11:42:50 +01:00
d140f93d25 Bugfix: hude club field when empty 2022-08-11 11:41:57 +01:00
26a6248a61 Tidied up nnecessary imports 2022-08-11 11:39:53 +01:00
9f8ea16974 Bugfix: button display 2022-08-11 11:01:29 +01:00
bc5ec44145 Bugfix default datetime 2022-08-11 10:59:12 +01:00
ff5b19fa0b Editing text: remove repetition 2022-08-11 10:24:15 +01:00
6c50be49c6 Bugfix: default time for exam creation 2022-08-11 10:23:40 +01:00
15 changed files with 83 additions and 21 deletions

View File

@ -1,5 +1,7 @@
SERVER_NAME= # URL where this will be hosted. SERVER_NAME= # URL where this will be hosted.
TZ=Europe/London # Time Zone
## Flask Configuration ## Flask Configuration
SECRET_KEY= # Long, secure, secret string. SECRET_KEY= # Long, secure, secret string.
DATA=./data/ DATA=./data/

3
.gitignore vendored
View File

@ -150,3 +150,6 @@ database/data/
# Ignore Encryption Keyfile # Ignore Encryption Keyfile
.encryption.key .encryption.key
# Ignore Data Dir
**/data/*

View File

@ -166,3 +166,59 @@ The app uses [OpenDyslexic](https://opendyslexic.org/), which is available on-li
It also has the option of rendering in other system fonts, but this can vary depending on your operating system. It also has the option of rendering in other system fonts, but this can vary depending on your operating system.
Because these are proprietary fonts, they cannot be made available on-line in the same way as open source ones should your system not have them. Because these are proprietary fonts, they cannot be made available on-line in the same way as open source ones should your system not have them.
Some fonts may not display correctly as a result. Some fonts may not display correctly as a result.
## Updating the Installation
If the app is updated, you can update the version on your installation using the following method:
### Navigate to the root folder
This will be the root folder into which you cloned the git repository when you set the app up.
### Stash your local changes
When you update the code, there is a risk the changes you made to your configuration will be overwritten.
To avoid this, use the following command:
```git stash```
This will stash the changes you made, and we can re-apply the changes once the new code has been downloaded.
If you do not have any other changes stashed, the index number of these changes should be `0` in a later step.
If there are other changes, make sure to note what the correct index number for the stashed changes is.
### Take down the Docker containers
We will need to stop the current containers with the following command:
```sudo docker compose down```
This may take a few seconds.
### Pull the updated code
Download the updated code from the Git repository:
```git pull```
This step might fail if you have any un-stashed local changed.
### Re-Apply your local configurations
Because we stashed our local configurations, we can re-apply them once again:
```git stash pop 0```
The index number (`0`) is assuming there were no other changes saved on your git repository.
If you have a different index number for the relevant changes from the above step, change this accordingly.
### Re-build the docker image
Now that we have the base code downloaded, we just need to update the docker image:
```sudo docker compose build app```
### Re-build the containers
This is the same last step as running the containers in the last step of the installation:
```sudo docker compose up -d```

View File

@ -2,7 +2,7 @@
{% block content %} {% block content %}
<div class="form-container"> <div class="form-container">
<form name="form-update-password" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for('admin._login') }}"> <form name="form-update-password" class="form-display form-post" action="{{ url_for('admin._update_password', **request.view_args) }}" data-rel-success="{{ url_for('admin._login') }}">
{% include "admin/components/server-alerts.html" %} {% include "admin/components/server-alerts.html" %}
<h2 class="form-heading">Update Password</h2> <h2 class="form-heading">Update Password</h2>
{{ form.hidden_tag() }} {{ form.hidden_tag() }}

View File

@ -24,7 +24,7 @@
</div> </div>
{{ entry.get_email() }} {{ entry.get_email() }}
</li> </li>
{% if entry.club %} {% if entry.get_club() %}
<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">
<h5 class="mb-1">Club</h5> <h5 class="mb-1">Club</h5>

View File

@ -22,7 +22,7 @@
</div> </div>
{{ entry.get_email() }} {{ entry.get_email() }}
</li> </li>
{% if entry.club %} {% if entry.get_club() %}
<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">
<h5 class="mb-1">Club</h5> <h5 class="mb-1">Club</h5>

View File

@ -40,7 +40,7 @@
{{ entry.get_surname() }}, {{ entry.get_first_name() }} {{ entry.get_surname() }}, {{ entry.get_first_name() }}
</td> </td>
<td> <td>
{% if entry.club %} {% if entry.get_club() %}
{{ entry.get_club() }} {{ entry.get_club() }}
{% endif %} {% endif %}
</td> </td>

View File

@ -9,7 +9,7 @@ from flask import abort, Blueprint, jsonify, render_template, redirect, request,
from flask.helpers import flash, url_for from flask.helpers import flash, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from datetime import date, datetime from datetime import date, datetime, timedelta
from json import loads from json import loads
from os import path from os import path
import secrets import secrets
@ -91,7 +91,7 @@ def _register():
flash(message=message, category='error') flash(message=message, category='error')
return jsonify({'error': message}), 401 return jsonify({'error': message}), 401
return send_errors_to_client(form=form) return send_errors_to_client(form=form)
return render_template('admin/auth/register.html', form=form) return render_template('/admin/auth/register.html', form=form)
@admin.route('/reset/', methods=['GET','POST']) @admin.route('/reset/', methods=['GET','POST'])
def _reset(): def _reset():
@ -117,7 +117,8 @@ def _reset():
user.clear_reset_tokens() user.clear_reset_tokens()
if request.args.get('verification') == verification_token: if request.args.get('verification') == verification_token:
form = UpdatePassword() form = UpdatePassword()
return render_template('/auth/update_password.html', form=form, user=user.id) session['user'] = user.id
return render_template('/admin/auth/update-password.html', form=form)
flash('The verification of your password reset request failed and the token has been invalidated. Please make a new reset password request.', 'error') flash('The verification of your password reset request failed and the token has been invalidated. Please make a new reset password request.', 'error')
return render_template('/admin/auth/reset.html', form=form) return render_template('/admin/auth/reset.html', form=form)
@ -126,7 +127,7 @@ def _reset():
def _update_password(): def _update_password():
form = UpdatePassword() form = UpdatePassword()
if form.validate_on_submit(): if form.validate_on_submit():
user = request.form.get('user') user = session.pop('user')
user = User.query.filter_by(id=user).first() user = User.query.filter_by(id=user).first()
user.update(password=request.form.get('password')) user.update(password=request.form.get('password'))
session['remembered_username'] = user.get_username() session['remembered_username'] = user.get_username()
@ -258,6 +259,8 @@ def _tests(filter:str=None):
if filter not in ['create','active','scheduled','expired','all']: return redirect(url_for('admin._tests', filter='active')) if filter not in ['create','active','scheduled','expired','all']: return redirect(url_for('admin._tests', filter='active'))
if filter == 'create': if filter == 'create':
form = CreateTest() form = CreateTest()
form.start_date.default = datetime.now()
form.expiry_date.default = date.today() + timedelta(days=1)
form.time_limit.choices = get_time_options() form.time_limit.choices = get_time_options()
form.dataset.choices = get_dataset_choices() form.dataset.choices = get_dataset_choices()
form.time_limit.default='none' form.time_limit.default='none'

View File

@ -6,8 +6,6 @@ from wtforms import BooleanField, IntegerField, PasswordField, SelectField, Stri
from wtforms.fields import DateTimeLocalField from wtforms.fields import DateTimeLocalField
from wtforms.validators import InputRequired, Email, EqualTo, Length, Optional from wtforms.validators import InputRequired, Email, EqualTo, Length, Optional
from datetime import date, datetime, timedelta
class Login(FlaskForm): class Login(FlaskForm):
username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)]) username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)])
password = PasswordField('Password', validators=[InputRequired(), Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')]) password = PasswordField('Password', validators=[InputRequired(), Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')])
@ -51,8 +49,8 @@ class UpdateAccount(FlaskForm):
password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')]) password_reenter = PasswordField('Re-Enter New Password', validators=[EqualTo('password', message='Passwords do not match.')])
class CreateTest(FlaskForm): class CreateTest(FlaskForm):
start_date = DateTimeLocalField('Start Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = datetime.now() ) start_date = DateTimeLocalField('Start Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()] )
expiry_date = DateTimeLocalField('Expiry Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()], default = date.today() + timedelta(days=1) ) expiry_date = DateTimeLocalField('Expiry Date', format='%Y-%m-%dT%H:%M', validators=[InputRequired()] )
time_limit = SelectField('Time Limit') time_limit = SelectField('Time Limit')
dataset = SelectField('Question Dataset') dataset = SelectField('Question Dataset')

View File

@ -194,7 +194,8 @@ class User(UserMixin, db.Model):
if entry.get_email() == email and not entry == self: return False, f'The email address {email} is already in use.' if entry.get_email() == email and not entry == self: return False, f'The email address {email} is already in use.'
self.set_email(email) self.set_email(email)
db.session.commit() db.session.commit()
write('system.log', f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.') _current_user = current_user.get_username() if current_user.is_authenticated else 'anonymous'
write('system.log', f'Information for user {self.get_username()} has been updated by {_current_user}.')
if notify: if notify:
message = Message( message = Message(
subject='RefTest | Account Update', subject='RefTest | Account Update',
@ -202,7 +203,7 @@ class User(UserMixin, db.Model):
bcc=[old_email,current_user.get_email()], bcc=[old_email,current_user.get_email()],
body=f""" body=f"""
Hello {self.get_username()},\n\n Hello {self.get_username()},\n\n
Your administrator account for the SKA RefTest App has been updated by {current_user.get_username()}.\n\n Your administrator account for the SKA RefTest App has been updated by {_current_user}.\n\n
Your new account details are as follows:\n\n Your new account details are as follows:\n\n
Email: {email}\n Email: {email}\n
Password: {password if password else '<same as old>'}\n\n Password: {password if password else '<same as old>'}\n\n
@ -213,7 +214,7 @@ class User(UserMixin, db.Model):
""", """,
html=f""" html=f"""
<p>Hello {self.get_username()},</p> <p>Hello {self.get_username()},</p>
<p>Your administrator account for the SKA RefTest App has been updated by {current_user.get_username()}.</p> <p>Your administrator account for the SKA RefTest App has been updated by {_current_user}.</p>
<p>Your new account details are as follows:</p> <p>Your new account details are as follows:</p>
<p>Email: {email} <br/> Password: <strong>{password if password else '&lt;same as old&gt;'}</strong></p> <p>Email: {email} <br/> Password: <strong>{password if password else '&lt;same as old&gt;'}</strong></p>
<p>You can update your email address and password by logging in to the admin console using the following URL:</p> <p>You can update your email address and password by logging in to the admin console using the following URL:</p>

View File

@ -146,7 +146,7 @@ $("#btn-start-quiz").click(function(event){
data: JSON.stringify({'id': id}), data: JSON.stringify({'id': id}),
contentType: "application/json", contentType: "application/json",
success: function(response) { success: function(response) {
$(this).fadeOut(); $("#btn-start-quiz").fadeOut();
$(".btn-quiz-return").fadeIn(); $(".btn-quiz-return").fadeIn();
$(".quiz-console").fadeIn(); $(".quiz-console").fadeIn();
$("#quiz-settings").fadeOut(); $("#quiz-settings").fadeOut();

View File

@ -123,7 +123,7 @@
<div class="container question-container quiz-start-text"> <div class="container question-container quiz-start-text">
<h4 class="question-title">Sample Question</h4> <h4 class="question-title">Sample Question</h4>
<p class="question-header"> <p class="question-header">
Korfball is a mixed-sex, controlled-contact, indoor, invasion ball sport. The sport originated in the Netherlands. It is a mixed-sex team sport. Its governing body is the International Korball Federation. There are numerous korfball leagues and associations around the world. A korfball match is officiated by a referee. Korfball is a mixed-sex, controlled-contact, indoor, invasion, team ball sport. The sport originated in the Netherlands. Its governing body is the International Korball Federation. There are numerous korfball leagues and associations around the world. A korfball match is officiated by a referee.
</p> </p>
<p class="question-text"> <p class="question-text">
In order to be a referee, what do you need to know? In order to be a referee, what do you need to know?

View File

@ -11,7 +11,7 @@
<strong class="results-details">Email Address</strong>: {{ entry.get_email() }} <br /> <strong class="results-details">Email Address</strong>: {{ entry.get_email() }} <br />
{% if entry.club %} {% if entry.get_club() %}
<strong class="results-details">Club</strong>: {{ entry.get_club() }} <br /> <strong class="results-details">Club</strong>: {{ entry.get_club() }} <br />
{% endif%} {% endif%}

View File

@ -57,5 +57,5 @@ def get_dataset_choices():
return dataset_choices return dataset_choices
def send_errors_to_client(form): def send_errors_to_client(form):
errors = [*form.errors] errors = [*form.errors.values()]
return jsonify({ 'error': errors}), 400 return jsonify({ 'error': errors}), 400

View File

@ -1 +0,0 @@
*