Added question viewer functionality

Added view questions panel to editor interface
Added view questions section of web site
Added links to navbars
This commit is contained in:
2022-08-17 16:32:58 +01:00
parent 294f1e42f7
commit 02290e968c
26 changed files with 1253 additions and 26 deletions

View File

@ -0,0 +1,84 @@
<!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.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
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') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('.static', filename='css/view.css') }}"
/>
{% block style %}
{% endblock %}
<title>{% block title %} SKA Referee Test | Admin Console {% endblock %}</title>
{% include "view/components/og-meta.html" %}
</head>
<body class="bg-light">
{% block navbar %}
{% include "view/components/navbar.html" %}
{% endblock %}
<div class="container">
{% block top_alerts %}
{% include "view/components/server-alerts.html" %}
{% endblock %}
{% block content %}{% endblock %}
</div>
<footer class="container site-footer mt-5">
{% block footer %}
{% include "view/components/footer.html" %}
{% endblock %}
</footer>
<!-- 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>

View File

@ -0,0 +1 @@
<div id="alert-box" tabindex="-1"></div>

View File

@ -0,0 +1,28 @@
{% extends "view/components/base.html" %}
{% block datatable_css %}
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/dataTables.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/2.0.1/css/buttons.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/colreorder/1.5.5/css/colReorder.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
{% endblock %}
{% block datatable_scripts %}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/dataTables.buttons.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.bootstrap5.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.colVis.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.html5.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.print.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/colreorder/1.5.5/js/dataTables.colReorder.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/fixedheader/3.2.0/js/dataTables.fixedHeader.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
{% endblock %}

View File

@ -0,0 +1,2 @@
<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&rsquo;s personal GIT repository</a> under an MIT License.</p>
<p>All questions in the test are &copy; The Scottish Korfball Association {{ now.year }}. All rights are reserved.</p>

View File

@ -0,0 +1,4 @@
{% extends "view/components/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block top_alerts %}
{% endblock %}

View File

@ -0,0 +1,117 @@
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
<div class="container">
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbar"
aria-controls="navbar"
aria-expanded="false"
aria-label="Toggle Navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbar">
<ul class="navbar-nav">
{% if not current_user.is_authenticated %}
<li class="nav-item" id="nav-login">
<a href="{{ url_for('admin._login') }}" id="link-login" class="nav-link">Log In</a>
</li>
{% endif %}
{% if current_user.is_authenticated %}
<li class="nav-item" id="nav-results">
<a href="{{ url_for('admin._view_entries') }}" id="link-results" class="nav-link">View Results</a>
</li>
<li class="nav-item dropdown" id="nav-tests">
<a
class="nav-link dropdown-toggle"
id="dropdown-tests"
role="button"
href="{{ url_for('admin._tests') }}"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Exams
</a>
<ul
class="dropdown-menu"
aria-labelledby="dropdown-settings"
>
<li>
<a href="{{ url_for('admin._tests', filter='active') }}" id="link-active" class="dropdown-item">Active</a>
</li>
<li>
<a href="{{ url_for('admin._tests', filter='scheduled') }}" id="link-scheduled" class="dropdown-item">Scheduled</a>
</li>
<li>
<a href="{{ url_for('admin._tests', filter='expired') }}" id="link-expired" class="dropdown-item">Expired</a>
</li>
<li>
<a href="{{ url_for('admin._tests', filter='all') }}" id="link-all" class="dropdown-item">All</a>
</li>
<li>
<a href="{{ url_for('admin._tests', filter='create') }}" id="link-create" class="dropdown-item">Create</a>
</li>
</ul>
</li>
<li class="nav-item dropdown" id="nav-settings">
<a
class="nav-link dropdown-toggle"
id="dropdown-account"
role="button"
href="{{ url_for('admin._settings') }}"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Settings
</a>
<ul
class="dropdown-menu"
aria-labelledby="dropdown-settings"
>
<li>
<a href="{{ url_for('admin._settings') }}" id="link-settings" class="dropdown-item">View Settings</a>
</li>
<li>
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
</li>
<li>
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Manage Questions</a>
</li>
<li>
<a href="{{ url_for('view._view') }}" id="link-editor" class="dropdown-item">View Questions</a>
</li>
<li>
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Edit Questions</a>
</li>
</ul>
</li>
<li class="nav-item dropdown" id="nav-account">
<a
class="nav-link dropdown-toggle"
id="dropdown-account"
role="button"
href="{{ url_for('admin._update_user', id=current_user.id) }}"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Account
</a>
<ul
class="dropdown-menu"
aria-labelledby="dropdown-account"
>
<li>
<a href="{{ url_for('admin._update_user', id=current_user.id) }}" id="link-account" class="dropdown-item">Account Settings</a>
</li>
<li>
<a href="{{ url_for('admin._logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
</li>
</ul>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,18 @@
<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 or {})) }}" />
<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" />
<link rel="shortcut icon" href="{{ url_for('.static', filename='favicon.ico') }}">

View File

@ -0,0 +1,23 @@
<div class="navbar navbar-expand-sm navbar-light bg-light">
<div class="container-fluid">
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin._tests', filter='active') }}">Active</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin._tests', filter='scheduled') }}">Scheduled</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin._tests', filter='expired') }}">Expired</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin._tests', filter='all') }}">All</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin._tests', filter='create') }}">Create</a>
</li>
</ul>
</div>
</div>
</div>

View 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 %}

View File

@ -0,0 +1,116 @@
{% extends "view/components/base.html" %}
{% block style %}
<link
rel="stylesheet"
href="{{ url_for('.static', filename='css/view.css') }}"
/>
{% endblock %}
{% block content %}
<h1>View Questions</h1>
<div class="container">
<p class="lead">
This page lists all the questions in the selected dataset.
</p>
</div>
<div class="container control-panel">
<button class="btn btn-primary" aria-title="Information" title="Information"><i class="bi bi-info-circle-fill"></i></button>
</div>
<div class="container info-panel">
<h3>
Information
</h3>
<p>
Questions in the test are arranged in blocks. Blocks can be of two types: <strong>Blocks</strong> of multiple related questions, and <strong>Single Questions</strong> that are not part of a block.
You can add, remove, or edit both Blockss and Questions through this editor.
</p>
<p>
<strong>Blocks</strong> are useful when you have a section of the test that contains multiple questions that are related to each other, for example if there is a scenario-based section where a series of questions are about the same situation.
</p>
<p>
Blocks can contain any number of questions within them, but cannot contain nested blocks.
</p>
<p>
When you set up a block, you can also add <strong>header text</strong> that will be displayed with each question.
You can use this to provide common information for a scenario across a series of questions.
</p>
<p>
Questions come in three types:
<ul>
<li>
<strong>Yes/No</strong> for when there is only a yes or no option,
</li>
<li>
<strong>Multiple Choice</strong> for your regular multiple choice questions, and
</li>
<li>
<strong>Ordered List</strong> for multiple choice questions that will be displayed in the same order as listed here.
</li>
</ul>
</p>
<p>
Normally, multiple choice questions will have the order of the options randomised.
</p>
<p>
Questions will be displayed to candidates in a randomised order.
Blocks of questions will be kept together, but the order within the block will also be randomised.
</p>
<p>
Questions can also be categorised using <strong>tags</strong>.
</p>
<p class="lead">
Placeholder for Questions Remaining in a Block
</p>
<p>
In order to show how many questions are remaining inside a block, e.g. to say &lsquo;the next n questions are about a specific scenario&rsquo;, the app uses the placeholder <code>&lt;block_remaining_questions&gt;</code>.
</p>
</div>
<div class="container viewer-panel">
<h3>
Question Dataset
</h3>
<div class="container dataset-metadata">
<div class="input-group mb-3">
<span class="input-group-text">Dataset Name</span>
<span class="form-control">
{{ dataset.get_name() }}
</span>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Author</span>
<span class="form-control">
{{ dataset.creator.get_username() }}
</span>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Last Updated</span>
<span class="form-control">
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
</span>
</div>
{% if dataset.default %}
<div class="input-group mb-3">
<span class="input-group-text">
<input type="checkbox" aria-label="Default" class="dataset-default" checked disabled>
</span>
<span class="form-control">
Default Dataset
</select>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block script %}
<script>
const target = "{{ url_for('api._editor') }}"
const id = "{{ dataset.id }}"
</script>
<script
type="text/javascript"
src="{{ url_for('.static', filename='js/view.js') }}"
></script>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "view/components/input-forms.html" %}
{% block content %}
<div class="form-container">
<form name="form-editor" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for(request.endpoint, **request.view_args) }}">
{% include "admin/components/server-alerts.html" %}
<h2 class="form">View Questions</h2>
{{ form.hidden_tag() }}
<div class="form-select-input">
{{ form.dataset(placeholder="Select Question Dataset") }}
{{ form.dataset.label }}
</div>
{% include "admin/components/client-alerts.html" %}
<div class="container form-submission-button">
<div class="row">
<div class="col text-center">
<button class="btn btn-md btn-success btn-block" type="submit">
<i class="bi bi-book-fill button-icon"></i>
View
</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}