Building new test form
Added CRUD for tests
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, BooleanField
|
||||
from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional
|
||||
from wtforms import StringField, PasswordField, BooleanField, DateField, SelectField
|
||||
from wtforms.validators import InputRequired, Email, Length, EqualTo, Optional, ValidationError
|
||||
from datetime import date, timedelta
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField('Username', validators=[InputRequired(), Length(min=4, max=15)])
|
||||
@ -41,4 +42,14 @@ class UpdateAccountForm(FlaskForm):
|
||||
password_confirm = PasswordField('Current Password', validators=[InputRequired(), Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')])
|
||||
email = StringField('Email Address', validators=[Optional(), Email(message='You must enter a valid email address.'), Length(max=50)])
|
||||
password = PasswordField('Change Password', validators=[Optional(),Length(min=6, max=30, message='The password must be between 6 and 20 characters long.')])
|
||||
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):
|
||||
time_options = [
|
||||
('none', 'None'),
|
||||
('60', '1 hour'),
|
||||
('90', '1 hour 30 minutes'),
|
||||
('120', '2 hours')
|
||||
]
|
||||
expiry = DateField('Expiry Date', format="%Y-%m-%d", validators=[InputRequired()], default = date.today() + timedelta(days=1) )
|
||||
time_limit = SelectField('Time Limit', choices=time_options)
|
45
ref-test/admin/models.py
Normal file
45
ref-test/admin/models.py
Normal file
@ -0,0 +1,45 @@
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
from flask import flash, jsonify
|
||||
|
||||
from main import db
|
||||
from security import encrypt, decrypt
|
||||
|
||||
class Test:
|
||||
def __init__(self, _id=None, expiry=None, time_limit=None, creator=None):
|
||||
self._id = _id
|
||||
self.expiry = expiry
|
||||
self.time_limit = None if time_limit == 'none' or time_limit == '' else time_limit
|
||||
self.creator = creator
|
||||
|
||||
def create(self):
|
||||
test = {
|
||||
'_id': self._id,
|
||||
'date': datetime.today(),
|
||||
'expiry': self.expiry,
|
||||
'time_limit': self.time_limit,
|
||||
'creator': encrypt(self.creator),
|
||||
'test_code': secrets.token_hex(6).upper()
|
||||
}
|
||||
if db.tests.insert_one(test):
|
||||
flash(f'Created a new exam with Exam Code <strong>{self.render_test_code(test["test_code"])}</strong>.', 'success')
|
||||
return jsonify({'success': test}), 200
|
||||
return jsonify({'error': f'Could not create exam. An error occurred.'}), 400
|
||||
|
||||
def add_user_code(self, user_code, time_adjustment):
|
||||
code = {
|
||||
'_id': uuid4().hex,
|
||||
'user_code': user_code,
|
||||
'time_adjustment': time_adjustment
|
||||
}
|
||||
if db.tests.find_one_and_update({'_id': self._id}, {'$push': {'time_adjustments': code}},upsert=False):
|
||||
return jsonify({'success': code})
|
||||
else:
|
||||
return jsonify({'error': 'An error occurred.'}), 400
|
||||
|
||||
def render_test_code(self, test_code):
|
||||
return '—'.join([test_code[:4], test_code[4:8], test_code[8:]])
|
||||
|
||||
def parse_test_code(self, test_code):
|
||||
return test_code.replace('—', '')
|
@ -27,7 +27,7 @@ body {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-signin-heading {
|
||||
.form-heading {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@ -65,6 +65,10 @@ body {
|
||||
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;
|
||||
}
|
||||
@ -149,6 +153,50 @@ table.dataTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alert-db-empty {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
font-size: 14pt;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.form-date-input, .form-select-input {
|
||||
position: relative;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.form-date-input input,
|
||||
.form-date-input label, .form-select-input select, .form-select-input label {
|
||||
padding: var(--input-padding-y) var(--input-padding-x);
|
||||
font-size: 16pt;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #585858;
|
||||
}
|
||||
|
||||
.datepicker::-webkit-calendar-picker-indicator {
|
||||
border: 1px;
|
||||
border-color: gray;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
.form-date-input label, .form-select-input 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;
|
||||
}
|
||||
|
||||
/* Fallback for Edge
|
||||
-------------------------------------------------- */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
|
@ -350,6 +350,49 @@ $('form[name=form-update-account]').submit(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
$('form[name=form-create-test]').submit(function(event) {
|
||||
|
||||
var $form = $(this);
|
||||
var alert = document.getElementById('alert-box');
|
||||
var data = $form.serialize();
|
||||
console.log(data)
|
||||
alert.innerHTML = ''
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
window.location.reload();
|
||||
},
|
||||
error: function(response) {
|
||||
console.log(response.responseJSON)
|
||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||
alert.innerHTML = alert.innerHTML + `
|
||||
<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) {
|
||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
||||
alert.innerHTML = alert.innerHTML + `
|
||||
<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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Dismiss Cookie Alert
|
||||
$('#dismiss-cookie-alert').click(function(event){
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="form-container">
|
||||
<form name="form-login" class="form-signin">
|
||||
{% include "admin/components/server-alerts.html" %}
|
||||
<h2 class="form-signin-heading">Log In</h2>
|
||||
<h2 class="form">Log In</h2>
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="form-label-group">
|
||||
{{ form.username(class_="form-control", autofocus=true, placeholder="Enter Username") }}
|
||||
|
@ -42,9 +42,6 @@
|
||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script>
|
||||
window.jQuery || document.write('<script src="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"
|
||||
|
27
ref-test/admin/templates/admin/components/datatable.html
Normal file
27
ref-test/admin/templates/admin/components/datatable.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "admin/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"/>
|
||||
{% 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>
|
||||
{% block custom_data_script %}{% endblock %}
|
||||
{% endblock %}
|
@ -4,26 +4,26 @@
|
||||
{% 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="Danger"></i>
|
||||
<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"></i>
|
||||
<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" title="Warning"></i>
|
||||
<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="Alert"></i>
|
||||
<i class="bi bi-info-circle-fill" title="Cookie Alert" aria-title="Cookie Alert"></i>
|
||||
{{ message|safe }}
|
||||
<button type="button" id="dismiss-cookie-alert" class="btn btn-success" data-bs-dismiss="alert" aria-label="Close">Accept</button>
|
||||
</div>
|
||||
|
@ -1,13 +1,5 @@
|
||||
{% extends "admin/components/base.html" %}
|
||||
{% extends "admin/components/datatable.html" %}
|
||||
{% block title %} SKA Referee Test | Manage Users {% endblock %}
|
||||
{% 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"/>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Manage Users</h1>
|
||||
|
||||
@ -120,22 +112,7 @@
|
||||
</form>
|
||||
</div>
|
||||
{% 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>
|
||||
{% block custom_data_script %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#user-table').DataTable({
|
||||
|
@ -1 +1,138 @@
|
||||
{% extends "admin/components/base.html" %}
|
||||
{% extends "admin/components/datatable.html" %}
|
||||
{% block title %} SKA Referee Test | Manage Exams {% endblock %}
|
||||
{% block content %}
|
||||
<h1>Manage Exams</h1>
|
||||
|
||||
{% if tests %}
|
||||
<table id="test-table" class="table table-striped" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-proority="1">
|
||||
Date Created
|
||||
</th>
|
||||
<th data-priority="2">
|
||||
Exam Code
|
||||
</th>
|
||||
<th data-priority="1">
|
||||
Expiry Date
|
||||
</th>
|
||||
<th data-proority="4">
|
||||
Time Limit
|
||||
</th>
|
||||
<th data-priority="3">
|
||||
Created By
|
||||
</th>
|
||||
<th data-priority="1">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for test in tests %}
|
||||
<tr class="user-table-row">
|
||||
<td>
|
||||
{{ test.date.date() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ '—'.join([test.test_code[:4], test.test_code[4:8], test.test_code[8:]]) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ test.expiry }}
|
||||
</td>
|
||||
<td>
|
||||
{% if test.time_limit == None -%}
|
||||
None
|
||||
{% elif test.time_limit == '60' -%}
|
||||
1 hour
|
||||
{% elif test.time_limit == '90' -%}
|
||||
1 hour 30 min
|
||||
{% elif test.time_limit == '120' -%}
|
||||
2 hours
|
||||
{% else -%}
|
||||
{{ test.time_limit }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ test.creator }}
|
||||
</td>
|
||||
<td class="test-row-actions">
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-primary"
|
||||
title="Edit Exam"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-lines-fill" viewBox="0 0 16 16">
|
||||
<path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-danger"
|
||||
title="Delete Exam"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-x-fill" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm6.146-2.854a.5.5 0 0 1 .708 0L14 6.293l1.146-1.147a.5.5 0 0 1 .708.708L14.707 7l1.147 1.146a.5.5 0 0 1-.708.708L14 7.707l-1.146 1.147a.5.5 0 0 1-.708-.708L13.293 7l-1.147-1.146a.5.5 0 0 1 0-.708z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% block custom_data_script %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#test-table').DataTable({
|
||||
'columnDefs': [
|
||||
{'sortable': false, 'targets': [1,3,5]},
|
||||
{'searchable': false, 'targets': [3,5]}
|
||||
],
|
||||
'order': [[0, 'desc'], [2, 'asc']],
|
||||
'buttons': [
|
||||
'copy', 'excel', 'pdf'
|
||||
],
|
||||
'responsive': 'true',
|
||||
'colReorder': 'true',
|
||||
'fixedHeader': 'true'
|
||||
});
|
||||
} );
|
||||
$('#test-table').show();
|
||||
$(window).trigger('resize');
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% else %}
|
||||
<div class="alert alert-primary alert-db-empty">
|
||||
<i class="bi bi-info-circle-fill" aria-title="Alert" title="Alert"></i>
|
||||
No exams have been created. Use the form below to create an exam.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-container">
|
||||
<form name="form-create-test" class="form-signin">
|
||||
<h2 class="form-signin-heading">Create Exam</h2>
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="form-date-input">
|
||||
{{ form.expiry(placeholder="Enter Expiry Date", class_ = "datepicker") }}
|
||||
{{ form.expiry.label }}
|
||||
</div>
|
||||
<div class="form-select-input">
|
||||
{{ form.time_limit(placeholder="Select Time Limit") }}
|
||||
{{ form.time_limit.label }}
|
||||
</div>
|
||||
{% include "admin/components/client-alerts.html" %}
|
||||
<div class="container form-submission-button">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<button title="Create Exam" class="btn btn-md btn-success btn-block" type="submit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 20 20">
|
||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
||||
</svg>
|
||||
Create Exam
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -10,25 +10,8 @@ from main import db
|
||||
from uuid import uuid4
|
||||
import secrets
|
||||
from main import mail
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .forms import CreateUserForm
|
||||
|
||||
cookie_consent = Blueprint(
|
||||
'cookie_consent',
|
||||
__name__
|
||||
)
|
||||
@cookie_consent.route('/')
|
||||
def _cookies():
|
||||
resp = redirect('/')
|
||||
resp.set_cookie(
|
||||
key = 'cookie_consent',
|
||||
value = 'True',
|
||||
max_age = timedelta(days=14) if request.cookies.get('remember') == 'True' else 'Session',
|
||||
path = '/',
|
||||
expires = datetime.utcnow() + timedelta(days=14) if request.cookies.get('remember') else 'Session'
|
||||
)
|
||||
return resp
|
||||
from datetime import datetime, date, timedelta
|
||||
from .models import Test
|
||||
|
||||
views = Blueprint(
|
||||
'admin_views',
|
||||
@ -99,6 +82,7 @@ def settings():
|
||||
@admin_account_required
|
||||
@login_required
|
||||
def users():
|
||||
from .forms import CreateUserForm
|
||||
form = CreateUserForm()
|
||||
if request.method == 'GET':
|
||||
users_list = decrypt_find(db.users, {})
|
||||
@ -259,8 +243,32 @@ def questions():
|
||||
def upload_questions():
|
||||
return render_template('/admin/settings/upload-questions.html')
|
||||
|
||||
@views.route('/tests/')
|
||||
@views.route('/tests/', methods=['GET','POST'])
|
||||
@admin_account_required
|
||||
@login_required
|
||||
def tests():
|
||||
return render_template('/admin/tests.html')
|
||||
from .forms import CreateTest
|
||||
form = CreateTest()
|
||||
form.time_limit.default='none'
|
||||
form.process()
|
||||
if request.method == 'GET':
|
||||
tests = decrypt_find(db.tests, {})
|
||||
return render_template('/admin/tests.html', tests = tests, form = form)
|
||||
if request.method == 'POST':
|
||||
if form.validate_on_submit():
|
||||
expiry = request.form.get('expiry')
|
||||
if datetime.strptime(expiry, '%Y-%m-%d').date() < date.today():
|
||||
return jsonify({'error': 'The expiry date cannot be in the past.'}), 400
|
||||
creator_id = get_id_from_cookie()
|
||||
creator = decrypt_find_one(db.users, { '_id': creator_id } )['username']
|
||||
test = Test(
|
||||
_id = uuid4().hex,
|
||||
expiry = expiry,
|
||||
time_limit = request.form.get('time_limit'),
|
||||
creator = creator
|
||||
)
|
||||
test.create()
|
||||
return jsonify({'success': 'New exam created.'}), 200
|
||||
else:
|
||||
errors = [*form.expiry.errors, *form.time_limit.errors]
|
||||
return jsonify({ 'error': errors}), 400
|
Reference in New Issue
Block a user