Finished common section of app
This commit is contained in:
parent
a8a01e17da
commit
08a140a73b
@ -1,32 +0,0 @@
|
|||||||
from main import app, Config
|
|
||||||
from .data import data
|
|
||||||
from .modules import db
|
|
||||||
from .tools.data import save
|
|
||||||
from .tools.logs import write
|
|
||||||
|
|
||||||
from os import mkdir, path, system
|
|
||||||
from cryptography.fernet import Fernet
|
|
||||||
|
|
||||||
from sqlalchemy_utils import database_exists, create_database
|
|
||||||
|
|
||||||
def install_scripts():
|
|
||||||
if not path.isdir(f'./{data}'): mkdir(f'./{data}')
|
|
||||||
if not path.isdir(f'./{data}/questions'): mkdir(f'./{data}/questions')
|
|
||||||
if not path.isfile(f'./{data}/.gitignore'):
|
|
||||||
with open(f'./{data}/.gitignore', 'a+') as file: file.write(f'*')
|
|
||||||
if not path.isfile(f'./{data}/config.json'): save({}, 'config.json')
|
|
||||||
if not path.isdir(f'./{data}/logs'): mkdir(f'./{data}/logs')
|
|
||||||
if not path.isfile(f'./{data}/logs/users.log'): write('users.log', 'Log file created.')
|
|
||||||
if not path.isfile(f'./{data}/logs/system.log'): write('system.log', 'Log file created.')
|
|
||||||
if not path.isfile(f'./{data}/logs/tests.log'): write('tests.log', 'Log file created.')
|
|
||||||
if not database_exists(Config.SQLALCHEMY_DATABASE_URI):
|
|
||||||
create_database(Config.SQLALCHEMY_DATABASE_URI)
|
|
||||||
write('system.log', 'No database found. Creating a new database.')
|
|
||||||
from .models import Entry, Test, User
|
|
||||||
with app.app_context(): db.create_all()
|
|
||||||
write('system.log', 'Creating database schema.')
|
|
||||||
if not path.isfile(f'./{data}/.encryption.key'):
|
|
||||||
write('system.log', 'No encryption key found. Generating new encryption key.')
|
|
||||||
with open(f'./{data}/.encryption.key', 'wb') as key_file:
|
|
||||||
key = Fernet.generate_key()
|
|
||||||
key_file.write(key)
|
|
@ -1,26 +0,0 @@
|
|||||||
{% extends "quiz/components/base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Privacy Policy</h1>
|
|
||||||
|
|
||||||
This web app stores data using cookies. The web site only stores the minimum information it needs to function.
|
|
||||||
<h5>Site Administrators</h5>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>For site administrators, this web site uses encrypted cookies to store data from your log-in session.</li>
|
|
||||||
<li>User information for administrators is encrypted and stored in a secure database, and are expunged when an account is deleted.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h5>Test Candidates</h5>
|
|
||||||
<ul>
|
|
||||||
<li>The web site will not be trackin your log in, and all information about your test attempt will be stored on your device until you submit it to the server.</li>
|
|
||||||
<li>Data from your test, including identifying information such as your name and email address, will be recorded by the Scottish Korfball Association in order to oversee the training and qualification of referees.</li>
|
|
||||||
<li>These records will be kept for three years or until the expiration of the theory exam qualification (whichever is later), and will be expunged securely thereafter.</li>
|
|
||||||
<li>All identifying information about candidates will be encrypted and stored in a secure database.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h5>Requests to Delete Data</h5>
|
|
||||||
<ul>
|
|
||||||
<li>You can request to have any of your data that is held here deleted by emailing <a href="mailto:refereeing@scotlandkorfball.co.uk">refereeing@scotlandkorfball.co.uk</a>.</li>
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
218
ref-test/app/root/css/style.css
Normal file
218
ref-test/app/root/css/style.css
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
body {
|
||||||
|
padding: 80px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cookie-alert {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dismiss-cookie-alert {
|
||||||
|
margin-top: 16px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instruction-container {
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 `<label>` 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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-name {
|
||||||
|
margin: 3rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-name .surname {
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-score {
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 36pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-score::after {
|
||||||
|
content: '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-grade {
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 26pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change Autocomplete styles in Chrome*/
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill:hover,
|
||||||
|
textarea:-webkit-autofill:focus,
|
||||||
|
select:-webkit-autofill,
|
||||||
|
select:-webkit-autofill:hover,
|
||||||
|
select:-webkit-autofill:focus {
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
ref-test/app/root/favicon.ico
Normal file
BIN
ref-test/app/root/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
ref-test/app/root/favicon.png
Normal file
BIN
ref-test/app/root/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
2
ref-test/app/root/js/jquery-3.6.0.min.js
vendored
Normal file
2
ref-test/app/root/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
86
ref-test/app/root/js/script.js
Normal file
86
ref-test/app/root/js/script.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
$("#od-font-test").click(function(){
|
||||||
|
$("body").css("font-family", "opendyslexic3regular")
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.test-code-input').keyup(function() {
|
||||||
|
var input = $(this).val().split("-").join("").split("—").join("");
|
||||||
|
if (input.length > 0) {
|
||||||
|
input = input.match(new RegExp('.{1,4}', 'g')).join("—");
|
||||||
|
}
|
||||||
|
$(this).val(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('form[name=form-quiz-start]').submit(function(event) {
|
||||||
|
|
||||||
|
var $form = $(this);
|
||||||
|
var data = $form.serialize();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: window.location.pathname,
|
||||||
|
type: 'POST',
|
||||||
|
data: data,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
var id = response.id
|
||||||
|
window.localStorage.setItem('id', id);
|
||||||
|
window.location.href = `/quiz/`;
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
error_response(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
function error_response(response) {
|
||||||
|
|
||||||
|
const $alert = $("#alert-box");
|
||||||
|
$alert.html('');
|
||||||
|
|
||||||
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
|
$alert.html(`
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
|
var output = ''
|
||||||
|
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
|
output += `
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error[i]}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$alert.html(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss Cookie Alert
|
||||||
|
$('#dismiss-cookie-alert').click(function(event){
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/cookies/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
time: Date.now()
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response){
|
||||||
|
console.log(response);
|
||||||
|
},
|
||||||
|
error: function(response){
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "quiz/components/base.html" %}
|
{% extends "components/base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Page Not Found</h1>
|
<h1>Page Not Found</h1>
|
78
ref-test/app/templates/components/base.html
Normal file
78
ref-test/app/templates/components/base.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('.static', filename='css/style.css') }}"
|
||||||
|
/>
|
||||||
|
{% block style %}
|
||||||
|
{% endblock %}
|
||||||
|
<title>{% block title %} SKA Referee Test Beta {% endblock %}</title>
|
||||||
|
{% include "components/og-meta.html" %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include "components/navbar.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div class="container quiz-container">
|
||||||
|
{% block top_alerts %}
|
||||||
|
{% include "components/server-alerts.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
<footer class="container site-footer">
|
||||||
|
{% include "components/footer.html" %}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JQuery, Popper, and Bootstrap js dependencies -->
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||||
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
window.jQuery || document.write(`<script src="{{ url_for('.static', filename='js/jquery-3.6.0.min.js') }}"><\/script>`)
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"
|
||||||
|
integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<!-- Custom js -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
var csrf_token = "{{ csrf_token() }}";
|
||||||
|
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="{{ url_for('.static', filename='js/script.js') }}"
|
||||||
|
></script>
|
||||||
|
{% block script %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
3
ref-test/app/templates/components/footer.html
Normal file
3
ref-test/app/templates/components/footer.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<p>This web app was developed by Vivek Santayana. The source code for the web app, excluding any data pertaining to the questions in the quiz, is freely available at <a href="https://git.vsnt.uk/viveksantayana/ska-referee-test">Vivek’s personal GIT repository</a> under an MIT License.</p>
|
||||||
|
<p>All questions in the test are © The Scottish Korfball Association {{ now.year }}. All rights are reserved.</p>
|
||||||
|
<p>OpenDyslexic 3 is an open source typeface created by Abbie Gonzalez, licensed under a <a href="https://scripts.sil.org/OFL">SIL-OFL</a>. More information about OpenDyslexic is available <a href="https://opendyslexic.org/">on the project web site</a>.</p>
|
14
ref-test/app/templates/components/navbar.html
Normal file
14
ref-test/app/templates/components/navbar.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav">
|
||||||
|
<div class="container">
|
||||||
|
<p class="navbar-brand mb-0 h1">SKA Refereeing Test (Beta)</p>
|
||||||
|
<div class="quiz-console w-100" style="display: none;" id="q-topbar">
|
||||||
|
<div class="d-flex justify-content align-middle">
|
||||||
|
<div class="container d-flex justify-content-center">
|
||||||
|
<span class="text-light q-timer" id="q-timer-widget" style="display: none;"><i class="bi bi-stopwatch-fill"></i> <span id="q-timer-display"></span></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-warning" aria-title="Question Grid" title="Question Grid" id="btn-toggle-navigator"><i class="bi bi-table"></i></a>
|
||||||
|
<a href="#" class="btn btn-danger" aria-title="Settings" title="Settings" id="btn-toggle-settings"><i class="bi bi-gear-fill"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
17
ref-test/app/templates/components/og-meta.html
Normal file
17
ref-test/app/templates/components/og-meta.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<meta name="description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta property="og:locale" content="en_UK" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta property="og:url" content="{{ url_for(request.endpoint, _external = True, **request.view_args) }}" />
|
||||||
|
<meta property="og:site_name" content="Scottish Korfball Association Referee Theory Exam" />
|
||||||
|
<meta property="og:image" content="{{ url_for('static', filename='favicon.png', _external = True) }}" />
|
||||||
|
<meta property="og:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||||
|
<meta property="og:image:width" content="512" />
|
||||||
|
<meta property="og:image:height" content="512" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta name="twitter:image" content="{{ url_for('static', filename='favicon.png', _external = True) }}" />
|
||||||
|
<meta name="twitter:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||||
|
<meta name="twitter:creator" content="@viveksantayana" />
|
||||||
|
<meta name="twitter:site" content="@viveksantayana" />
|
||||||
|
<meta name="theme-color" content="#343a40" />
|
43
ref-test/app/templates/components/server-alerts.html
Normal file
43
ref-test/app/templates/components/server-alerts.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% set cookie_flash_flag = namespace(value=False) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
{% if category == "error" %}
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Error" aria-title="Error"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "success" %}
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-check2-circle" title="Success" aria-title="Success"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "warning" %}
|
||||||
|
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" aria-title="Warning" title="Warning"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "cookie_alert" %}
|
||||||
|
{% if not cookie_flash_flag.value %}
|
||||||
|
<div class="alert alert-primary alert-dismissible fade show" id="cookie-alert" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" title="Cookie Alert" aria-title="Cookie Alert"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<div class="d-flex justify-content-center w-100">
|
||||||
|
<button type="button" id="dismiss-cookie-alert" class="btn btn-success" data-bs-dismiss="alert" aria-label="Close">Accept</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% set cookie_flash_flag.value = True %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" title="Alert"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
@ -1 +1,26 @@
|
|||||||
<h1>Privacy Policy</h1>
|
{% extends "components/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
|
||||||
|
This web app stores data using cookies. The web site only stores the minimum information it needs to function.
|
||||||
|
<h5>Site Administrators</h5>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>For site administrators, this web site uses encrypted cookies to store data from your log-in session.</li>
|
||||||
|
<li>User information for administrators is encrypted and stored in a secure database, and are expunged when an account is deleted.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h5>Test Candidates</h5>
|
||||||
|
<ul>
|
||||||
|
<li>The web site will not be trackin your log in, and all information about your test attempt will be stored on your device until you submit it to the server.</li>
|
||||||
|
<li>Data from your test, including identifying information such as your name and email address, will be recorded by the Scottish Korfball Association in order to oversee the training and qualification of referees.</li>
|
||||||
|
<li>These records will be kept for three years or until the expiration of the theory exam qualification (whichever is later), and will be expunged securely thereafter.</li>
|
||||||
|
<li>All identifying information about candidates will be encrypted and stored in a secure database.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h5>Requests to Delete Data</h5>
|
||||||
|
<ul>
|
||||||
|
<li>You can request to have any of your data that is held here deleted by emailing <a href="mailto:refereeing@scotlandkorfball.co.uk">refereeing@scotlandkorfball.co.uk</a>.</li>
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
@ -8,7 +8,7 @@ views = Blueprint(
|
|||||||
name='views',
|
name='views',
|
||||||
import_name=__name__,
|
import_name=__name__,
|
||||||
template_folder='templates',
|
template_folder='templates',
|
||||||
static_folder='static'
|
static_folder='root',
|
||||||
)
|
)
|
||||||
|
|
||||||
@views.route('/privacy/')
|
@views.route('/privacy/')
|
||||||
|
@ -1 +1 @@
|
|||||||
from app.config import DevelopmentConfig as Config
|
from app.config import ProductionConfig as Config
|
@ -1,14 +1,20 @@
|
|||||||
from app.models import User
|
from app.data import data
|
||||||
|
from app.models import Entry, Dataset, Test, User
|
||||||
from app.modules import bootstrap, csrf, db, login_manager, mail
|
from app.modules import bootstrap, csrf, db, login_manager, mail
|
||||||
|
from app.tools.data import save
|
||||||
|
from app.tools.logs import write
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
from flask import flash, Flask, request
|
from flask import flash, Flask, render_template, request
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from flask_wtf.csrf import CSRFError
|
from flask_wtf.csrf import CSRFError
|
||||||
|
from sqlalchemy_utils import database_exists, create_database
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from os import mkdir, path
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@ -36,7 +42,7 @@ def create_app():
|
|||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def _404_handler(error):
|
def _404_handler(error):
|
||||||
return jsonify({'error':'404 — Not Found'}), 404
|
return render_template('404.html')
|
||||||
@app.errorhandler(CSRFError)
|
@app.errorhandler(CSRFError)
|
||||||
def _csrf_handler():
|
def _csrf_handler():
|
||||||
return jsonify({'error':'Could not validate a secure connection.'}), 403
|
return jsonify({'error':'Could not validate a secure connection.'}), 403
|
||||||
@ -54,11 +60,28 @@ def create_app():
|
|||||||
app.register_blueprint(views)
|
app.register_blueprint(views)
|
||||||
app.register_blueprint(quiz)
|
app.register_blueprint(quiz)
|
||||||
|
|
||||||
|
if not path.isdir(f'./{data}'): mkdir(f'./{data}')
|
||||||
|
if not path.isdir(f'./{data}/questions'): mkdir(f'./{data}/questions')
|
||||||
|
if not path.isfile(f'./{data}/.gitignore'):
|
||||||
|
with open(f'./{data}/.gitignore', 'a+') as file: file.write(f'*')
|
||||||
|
if not path.isfile(f'./{data}/config.json'): save({}, 'config.json')
|
||||||
|
if not path.isdir(f'./{data}/logs'): mkdir(f'./{data}/logs')
|
||||||
|
if not path.isfile(f'./{data}/logs/users.log'): write('users.log', 'Log file created.')
|
||||||
|
if not path.isfile(f'./{data}/logs/system.log'): write('system.log', 'Log file created.')
|
||||||
|
if not path.isfile(f'./{data}/logs/tests.log'): write('tests.log', 'Log file created.')
|
||||||
|
if not database_exists(Config.SQLALCHEMY_DATABASE_URI):
|
||||||
|
create_database(Config.SQLALCHEMY_DATABASE_URI)
|
||||||
|
write('system.log', 'No database found. Creating a new database.')
|
||||||
|
with app.app_context(): db.create_all()
|
||||||
|
write('system.log', 'Creating database schema.')
|
||||||
|
if not path.isfile(f'./{data}/.encryption.key'):
|
||||||
|
write('system.log', 'No encryption key found. Generating new encryption key.')
|
||||||
|
with open(f'./{data}/.encryption.key', 'wb') as key_file:
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
key_file.write(key)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from app.install import install_scripts
|
|
||||||
install_scripts()
|
|
||||||
app.run()
|
app.run()
|
@ -1,6 +1,4 @@
|
|||||||
from main import app
|
from main import app
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from app.install import install_scripts
|
|
||||||
install_scripts()
|
|
||||||
app.run()
|
app.run()
|
Loading…
Reference in New Issue
Block a user