Added a whole lot of views.
Finished quiz API views Finished question generator and answer eval
This commit is contained in:
parent
a58f267586
commit
126bf9203c
@ -1,6 +1,7 @@
|
|||||||
from ..forms.admin import CreateUser, Login, Register, ResetPassword, UpdatePassword
|
from ..forms.admin import CreateUser, DeleteUser, Login, Register, ResetPassword, UpdatePassword, UpdateUser, UploadData
|
||||||
from ..models import User
|
from ..models import Dataset, User
|
||||||
from ..tools.auth import disable_if_logged_in, require_account_creation
|
from ..tools.auth import disable_if_logged_in, require_account_creation
|
||||||
|
from ..tools.data import check_is_json, validate_json
|
||||||
|
|
||||||
from flask import Blueprint, flash, jsonify, render_template, redirect, request, session
|
from flask import Blueprint, flash, jsonify, render_template, redirect, request, session
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
@ -18,12 +19,16 @@ admin = Blueprint(
|
|||||||
@admin.route('/')
|
@admin.route('/')
|
||||||
@admin.route('/home/')
|
@admin.route('/home/')
|
||||||
@admin.route('/dashboard/')
|
@admin.route('/dashboard/')
|
||||||
|
@login_required
|
||||||
def _home():
|
def _home():
|
||||||
return 'Home Page'
|
return 'Home Page' # TODO Dashboard
|
||||||
|
|
||||||
@admin.route('/settings/')
|
@admin.route('/settings/')
|
||||||
|
@login_required
|
||||||
def _settings():
|
def _settings():
|
||||||
return 'Settings Page'
|
users = User.query.all()
|
||||||
|
datasets = Dataset.query.all()
|
||||||
|
return render_template('/admin/settings/index.html', users=users, datasets=datasets)
|
||||||
|
|
||||||
@admin.route('/login/', methods=['GET','POST'])
|
@admin.route('/login/', methods=['GET','POST'])
|
||||||
@disable_if_logged_in
|
@disable_if_logged_in
|
||||||
@ -64,7 +69,6 @@ def _register():
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
new_user = User()
|
new_user = User()
|
||||||
new_user.generate_id()
|
|
||||||
new_user.set_username = request.form.get('username').lower()
|
new_user.set_username = request.form.get('username').lower()
|
||||||
new_user.set_email = request.form.get('email').lower()
|
new_user.set_email = request.form.get('email').lower()
|
||||||
new_user.set_password = request.form.get('password').lower()
|
new_user.set_password = request.form.get('password').lower()
|
||||||
@ -140,3 +144,86 @@ def _users():
|
|||||||
errors = [*form.username.errors, *form.email.errors, *form.password.errors]
|
errors = [*form.username.errors, *form.email.errors, *form.password.errors]
|
||||||
return jsonify({ 'error': errors}), 401
|
return jsonify({ 'error': errors}), 401
|
||||||
return render_template('/admin/settings/users.html', form = form, users = users)
|
return render_template('/admin/settings/users.html', form = form, users = users)
|
||||||
|
|
||||||
|
@admin.route('/settings/users/delete/<string:id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def _delete_user(id:str):
|
||||||
|
user = User.query.filter_by(id=id).first()
|
||||||
|
form = DeleteUser()
|
||||||
|
if request.method == 'POST':
|
||||||
|
if not user: return jsonify({'error': 'User does not exist.'}), 400
|
||||||
|
if id == current_user.id: return jsonify({'error': 'Cannot delete your own account.'}), 400
|
||||||
|
if form.validate_on_submit():
|
||||||
|
password = request.form.get('password')
|
||||||
|
if not current_user.verify_password(password): return jsonify({'error': 'The password you entered is incorrect.'}), 401
|
||||||
|
success, message = user.delete(notify=request.form.get('notify'))
|
||||||
|
if success: return jsonify({'success': message}), 200
|
||||||
|
return jsonify({'error': message}), 400
|
||||||
|
errors = form.password.errors
|
||||||
|
return jsonify({ 'error': errors}), 400
|
||||||
|
|
||||||
|
if id == current_user.id:
|
||||||
|
flash('Cannot delete your own user account.', 'error')
|
||||||
|
return redirect(url_for('admin._users'))
|
||||||
|
if not user:
|
||||||
|
flash('User not found.', 'error')
|
||||||
|
return redirect(url_for('admin._users'))
|
||||||
|
return render_template('/admin/settings/delete_user.html', form=form, id = id, user = user)
|
||||||
|
|
||||||
|
@admin.route('/settings/users/update/<string:id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def _update_user(id:str):
|
||||||
|
user = User.query.filter_by(id=id).first()
|
||||||
|
form = UpdateUser()
|
||||||
|
if request.method == 'POST':
|
||||||
|
if not user: return jsonify({'error': 'User does not exist.'}), 400
|
||||||
|
if form.validate_on_submit():
|
||||||
|
success, message = user.update(
|
||||||
|
password = request.form.get('password'),
|
||||||
|
email = request.form.get('email'),
|
||||||
|
notify = request.form.get('notify')
|
||||||
|
)
|
||||||
|
if success: return jsonify({'success': message}), 200
|
||||||
|
return jsonify({'error': message}), 400
|
||||||
|
errors = [*form.confirm_password.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors]
|
||||||
|
return jsonify({ 'error': errors}), 400
|
||||||
|
if not user:
|
||||||
|
flash('User not found.', 'error')
|
||||||
|
return redirect(url_for('admin._users'))
|
||||||
|
return render_template('/admin/settings/delete_user.html', form=form, id = id, user = user)
|
||||||
|
|
||||||
|
@admin.route('/settings/questions/', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def _quesitons():
|
||||||
|
form = UploadData()
|
||||||
|
if request.method == 'POST':
|
||||||
|
if form.validate_on_submit():
|
||||||
|
upload = form.data_file.data
|
||||||
|
if not check_is_json(upload): return jsonify({'error': 'Invalid file. Please upload a JSON file.'}), 400
|
||||||
|
if not validate_json(upload): return jsonify({'error': 'The data in the file is invalid.'}), 400 # TODO Perhaps make a more complex validation script
|
||||||
|
new_dataset = Dataset()
|
||||||
|
success, message = new_dataset.create(
|
||||||
|
upload = upload,
|
||||||
|
default = request.form.get('default')
|
||||||
|
)
|
||||||
|
if success: return jsonify({'success': message}), 200
|
||||||
|
return jsonify({'error': message}), 400
|
||||||
|
errors = form.data_file.errors
|
||||||
|
return jsonify({ 'error': errors}), 400
|
||||||
|
|
||||||
|
data = Dataset.query.all()
|
||||||
|
return render_template('/admin/settings/questions.html', data=data)
|
||||||
|
|
||||||
|
@admin.route('/settings/questions/edit/', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def _delete_questions():
|
||||||
|
id = request.get_json()['id']
|
||||||
|
action = request.get_json()['action']
|
||||||
|
dataset = Dataset.query.filter_by(id=id).first()
|
||||||
|
if action == 'delete': success, message = dataset.delete()
|
||||||
|
elif action == 'default': success, message = dataset.make_default()
|
||||||
|
if success: return jsonify({'success': message}), 200
|
||||||
|
return jsonify({'error': message}), 400
|
||||||
|
|
||||||
|
# TODO Test views
|
||||||
|
# TODO Result views
|
@ -1,4 +1,10 @@
|
|||||||
from flask import Blueprint
|
from ..models import Dataset, Entry
|
||||||
|
from ..tools.test import evaluate_answers, generate_questions
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from json import loads
|
||||||
|
|
||||||
api = Blueprint(
|
api = Blueprint(
|
||||||
name='api',
|
name='api',
|
||||||
@ -7,8 +13,54 @@ api = Blueprint(
|
|||||||
|
|
||||||
@api.route('/questions/', methods=['POST'])
|
@api.route('/questions/', methods=['POST'])
|
||||||
def _fetch_questions():
|
def _fetch_questions():
|
||||||
return 'Fetch Questions'
|
id = request.get_json()['id']
|
||||||
|
entry = Entry.query.filter_by(id=id).first()
|
||||||
|
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400
|
||||||
|
test = entry['test']
|
||||||
|
user_code = entry['user_code']
|
||||||
|
time_limit = test['time_limit']
|
||||||
|
time_adjustment = 0
|
||||||
|
if time_limit:
|
||||||
|
_time_limit = int(time_limit)
|
||||||
|
if user_code:
|
||||||
|
time_adjustment = test['time_adjustments'][user_code]
|
||||||
|
_time_limit += time_adjustment
|
||||||
|
end_delta = timedelta(minutes=_time_limit)
|
||||||
|
end_time = datetime.utcnow() + end_delta
|
||||||
|
else:
|
||||||
|
end_time = None
|
||||||
|
entry.start()
|
||||||
|
dataset = test['dataset']
|
||||||
|
success, message = dataset.check_file()
|
||||||
|
if not success: return jsonify({'error': message}), 500
|
||||||
|
data_path = dataset.get_file()
|
||||||
|
with open(data_path, 'r') as data_file:
|
||||||
|
data = loads(data_file.read())
|
||||||
|
questions = generate_questions(data)
|
||||||
|
return jsonify({
|
||||||
|
'time_limit': end_time,
|
||||||
|
'questions': questions,
|
||||||
|
'start_time': entry['start_time'],
|
||||||
|
'time_adjustment': time_adjustment
|
||||||
|
}), 200
|
||||||
|
|
||||||
@api.route('/submit/', methods=['POST'])
|
@api.route('/submit/', methods=['POST'])
|
||||||
def _submit_quiz():
|
def _submit_quiz():
|
||||||
return 'Submit Quiz'
|
id = request.get_json()['id']
|
||||||
|
answers = request.get_json()['answers']
|
||||||
|
entry = Entry.query.filter_by(id=id).first()
|
||||||
|
if not entry: return jsonify({'error': 'Unrecognised ID.'}), 400
|
||||||
|
test = entry['test']
|
||||||
|
dataset = test['dataset']
|
||||||
|
success, message = dataset.check_file()
|
||||||
|
if not success: return jsonify({'error': message}), 500
|
||||||
|
data_path = dataset.get_file()
|
||||||
|
with open(data_path, 'r') as data_file:
|
||||||
|
data = loads(data_file.read())
|
||||||
|
result = evaluate_answers(answers=answers, key=data)
|
||||||
|
entry.complete(answers=answers, result=result)
|
||||||
|
return jsonify({
|
||||||
|
'success': 'Your submission has been processed. Redirecting you to receive your results.',
|
||||||
|
'id': id
|
||||||
|
}), 200
|
||||||
|
|
@ -11,6 +11,7 @@ from sqlalchemy_utils import database_exists, create_database
|
|||||||
|
|
||||||
def install_scripts():
|
def install_scripts():
|
||||||
if not path.isdir(f'./{data}'): mkdir(f'./{data}')
|
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'):
|
if not path.isfile(f'./{data}/.gitignore'):
|
||||||
with open(f'./{data}/.gitignore', 'a+') as file: file.write(f'*')
|
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.isfile(f'./{data}/config.json'): save({}, 'config.json')
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
from .entry import Entry
|
from .entry import Entry
|
||||||
from .test import Test
|
from .test import Test
|
||||||
from .user import User
|
from .user import User
|
||||||
|
from .dataset import Dataset
|
82
ref-test/app/models/dataset.py
Normal file
82
ref-test/app/models/dataset.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
from ..data import data
|
||||||
|
from ..modules import db
|
||||||
|
from ..tools.logs import write
|
||||||
|
|
||||||
|
from flask import flash, jsonify
|
||||||
|
from flask_login import current_user
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from json import dump, loads
|
||||||
|
from os import path, remove
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
class Dataset(db.Model):
|
||||||
|
|
||||||
|
id = db.Column(db.String(36), primary_key=True)
|
||||||
|
tests = db.relationship('Test', backref='dataset')
|
||||||
|
creator_id = db.Column(db.String(36), db.ForeignKey('user.id'))
|
||||||
|
date = db.Column(db.DateTime, nullable=False)
|
||||||
|
default = db.Column(db.Boolean, default=False, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Dataset {self.id}> was added.'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
|
generate_id.setter
|
||||||
|
def generate_id(self): self.id = uuid4.hex()
|
||||||
|
|
||||||
|
def make_default(self):
|
||||||
|
for dataset in Dataset.query.all():
|
||||||
|
dataset.default = False
|
||||||
|
self.default = True
|
||||||
|
db.session.commit()
|
||||||
|
write('system.log', f'Dataset {self.id} set as default by {current_user.get_username()}.')
|
||||||
|
flash(message='Dataset set as default.', category='success')
|
||||||
|
return True, f'Dataset set as default.'
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
if self.default:
|
||||||
|
message = 'Cannot delete the default dataset.'
|
||||||
|
flash(message, 'error')
|
||||||
|
return False, jsonify({'error': message})
|
||||||
|
if Dataset.query.all().count() == 1:
|
||||||
|
message = 'Cannot delete the only dataset.'
|
||||||
|
flash(message, 'error')
|
||||||
|
return False, jsonify({'error': message})
|
||||||
|
write('system.log', f'Dataset {self.id} deleted by {current_user.get_username()}.')
|
||||||
|
filename = secure_filename('.'.join([self.id,'json']))
|
||||||
|
file_path = path.join(data, 'questions', filename)
|
||||||
|
remove(file_path)
|
||||||
|
db.session.delete(self)
|
||||||
|
db.session.commit()
|
||||||
|
return True, 'Dataset deleted.'
|
||||||
|
|
||||||
|
def create(self, upload, default:bool=False):
|
||||||
|
self.generate_id()
|
||||||
|
timestamp = datetime.now()
|
||||||
|
filename = secure_filename('.'.join([self.id,'json']))
|
||||||
|
file_path = path.join(data, 'questions', filename)
|
||||||
|
upload.stream.seek(0)
|
||||||
|
questions = loads(upload.read())
|
||||||
|
with open(file_path, 'w') as file:
|
||||||
|
dump(questions, file, indent=2)
|
||||||
|
self.date = timestamp
|
||||||
|
self.creator = current_user
|
||||||
|
if default: self.make_default()
|
||||||
|
write('system.log', f'New dataset {self.id} added by {current_user.get_username()}.')
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
return True, 'Dataset uploaded.'
|
||||||
|
|
||||||
|
def check_file(self):
|
||||||
|
filename = secure_filename('.'.join([self.id,'json']))
|
||||||
|
file_path = path.join(data, 'questions', filename)
|
||||||
|
if not path.isfile(file_path): return False, 'Data file is missing.'
|
||||||
|
|
||||||
|
def get_file(self):
|
||||||
|
filename = secure_filename('.'.join([self.id,'json']))
|
||||||
|
file_path = path.join(data, 'questions', filename)
|
||||||
|
return file_path
|
@ -8,6 +8,7 @@ from flask import jsonify
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
class Entry(db.Model):
|
class Entry(db.Model):
|
||||||
|
|
||||||
@ -25,6 +26,15 @@ class Entry(db.Model):
|
|||||||
answers = db.Column(JsonEncodedDict, nullable=True)
|
answers = db.Column(JsonEncodedDict, nullable=True)
|
||||||
result = db.Column(JsonEncodedDict, nullable=True)
|
result = db.Column(JsonEncodedDict, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<New entry by {self.first_name} {self.surname}> was added with <id {self.id}>.'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
|
generate_id.setter
|
||||||
|
def generate_id(self): self.id = uuid4.hex()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def set_first_name(self): raise AttributeError('set_first_name is not a readable attribute.')
|
def set_first_name(self): raise AttributeError('set_first_name is not a readable attribute.')
|
||||||
|
|
||||||
@ -63,10 +73,11 @@ class Entry(db.Model):
|
|||||||
write('tests.log', f'New test started by {self.get_first_name()} {self.get_surname()}.')
|
write('tests.log', f'New test started by {self.get_first_name()} {self.get_surname()}.')
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def complete(self):
|
def complete(self, answers:dict=None, result:dict=None):
|
||||||
self.end_time = datetime.now()
|
self.end_time = datetime.now()
|
||||||
|
self.answers = answers
|
||||||
write('tests.log', f'Test completed by {self.get_first_name()} {self.get_surname()}.')
|
write('tests.log', f'Test completed by {self.get_first_name()} {self.get_surname()}.')
|
||||||
delta = timedelta(minutes=self.test.time_limit)
|
delta = timedelta(minutes=self.test.time_limit+1)
|
||||||
if not self.test.time_limit or self.end_time <= self.start_time + delta:
|
if not self.test.time_limit or self.end_time <= self.start_time + delta:
|
||||||
self.status = 'finished'
|
self.status = 'finished'
|
||||||
self.valid = True
|
self.valid = True
|
||||||
|
@ -11,7 +11,7 @@ from datetime import datetime
|
|||||||
from json import dump, loads
|
from json import dump, loads
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
class Test(db.Model):
|
class Test(db.Model):
|
||||||
|
|
||||||
@ -21,18 +21,33 @@ class Test(db.Model):
|
|||||||
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'))
|
||||||
data = db.Column(db.String(36), nullable=False)
|
dataset_id = db.Column(db.String(36), db.ForeignKey('dataset.id'))
|
||||||
adjustments = db.Column(JsonEncodedDict, nullable=True)
|
adjustments = db.Column(JsonEncodedDict, nullable=True)
|
||||||
entries = db.relationship('Entry', backref='test')
|
entries = db.relationship('Entry', backref='test')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<test with code {self.code} was created by {current_user.get_username()}.>'
|
return f'<test with code {self.code} was created by {current_user.get_username()}.>'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generate_id(self): raise AttributeError('generate_id is not a readable attribute.')
|
||||||
|
|
||||||
|
generate_id.setter
|
||||||
|
def generate_id(self): self.id = uuid4.hex()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generate_code(self): raise AttributeError('generate_code is not a readable attribute.')
|
||||||
|
|
||||||
|
generate_code.setter
|
||||||
|
def generate_code(self): self.code = secrets.token_hex(6).lower()
|
||||||
|
|
||||||
def get_code(self):
|
def get_code(self):
|
||||||
code = self.code.upper()
|
code = self.code.upper()
|
||||||
return '—'.join([code[:4], code[4:8], code[8:]])
|
return '—'.join([code[:4], code[4:8], code[8:]])
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
|
self.generate_id()
|
||||||
|
self.generate_code()
|
||||||
|
self.creator = current_user
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
write('system.log', f'Test with code {self.code} created by {current_user.get_username()}.')
|
write('system.log', f'Test with code {self.code} created by {current_user.get_username()}.')
|
||||||
|
@ -17,6 +17,7 @@ class User(UserMixin, db.Model):
|
|||||||
reset_token = db.Column(db.String(20), nullable=True)
|
reset_token = db.Column(db.String(20), 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')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<user {self.username}> was added with <id {self.id}>.'
|
return f'<user {self.username}> was added with <id {self.id}>.'
|
||||||
@ -52,6 +53,7 @@ class User(UserMixin, db.Model):
|
|||||||
def get_email(self): return decrypt(self.email)
|
def get_email(self): return decrypt(self.email)
|
||||||
|
|
||||||
def register(self, notify:bool=False):
|
def register(self, notify:bool=False):
|
||||||
|
self.generate_id()
|
||||||
users = User.query.all()
|
users = User.query.all()
|
||||||
for user in users:
|
for user in users:
|
||||||
if user.get_username() == self.get_username(): return False, f'Username {self.get_username()} already in use.'
|
if user.get_username() == self.get_username(): return False, f'Username {self.get_username()} already in use.'
|
||||||
@ -88,17 +90,19 @@ class User(UserMixin, db.Model):
|
|||||||
self.reset_token = self.verification_token = None
|
self.reset_token = self.verification_token = None
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self, notify:bool=False):
|
||||||
username = self.get_username()
|
username = self.get_username()
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
write('users.log', f'User \'{username}\' was deleted by \'{current_user.get_username()}\'.')
|
message = f'User \'{username}\' was deleted by \'{current_user.get_username()}\'.'
|
||||||
|
write('users.log', message)
|
||||||
|
return True, message
|
||||||
|
|
||||||
def update(self, password:str=None, email:str=None):
|
def update(self, password:str=None, email:str=None, notify:bool=False):
|
||||||
if not password and not email: return False, jsonify({'error': 'There were no changes requested.'})
|
if not password and not email: return False, jsonify({'error': 'There were no changes requested.'})
|
||||||
if password: self.set_password(password)
|
if password: self.set_password(password)
|
||||||
if email: self.set_email(email)
|
if email: self.set_email(email)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.'
|
message = f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.'
|
||||||
write('system.log', message)
|
write('system.log', message)
|
||||||
return True, jsonify({'success': message})
|
return True, message
|
||||||
|
1
ref-test/app/templates/privacy.html
Normal file
1
ref-test/app/templates/privacy.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1>Privacy Policy</h1>
|
@ -1,5 +1,7 @@
|
|||||||
from ..data import data as data_dir
|
from ..data import data as data_dir
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from random import shuffle
|
||||||
|
|
||||||
def load(filename:str):
|
def load(filename:str):
|
||||||
with open(f'./{data_dir}/{filename}') as file:
|
with open(f'./{data_dir}/{filename}') as file:
|
||||||
@ -8,3 +10,25 @@ def load(filename:str):
|
|||||||
def save(data:dict, filename:str):
|
def save(data:dict, filename:str):
|
||||||
with open(f'./{data_dir}/{filename}', 'w') as file:
|
with open(f'./{data_dir}/{filename}', 'w') as file:
|
||||||
json.dump(data, file, indent=4)
|
json.dump(data, file, indent=4)
|
||||||
|
|
||||||
|
def check_is_json(file):
|
||||||
|
if not '.' in file.filename or not file.filename.rsplit('.',1)[-1] == 'json': return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_json(file):
|
||||||
|
file.stream.seek(0)
|
||||||
|
data = json.loads(file.read())
|
||||||
|
if not type(data) is list: return False
|
||||||
|
|
||||||
|
def randomise_list(list:list):
|
||||||
|
_list = list.copy()
|
||||||
|
shuffle(_list)
|
||||||
|
return(_list)
|
||||||
|
|
||||||
|
def get_tag_list(dataset:list):
|
||||||
|
output = []
|
||||||
|
for block in dataset:
|
||||||
|
if block['type'] == 'question': output = list(set(output) | set(block['tags']))
|
||||||
|
if block['type'] == 'block':
|
||||||
|
for question in block['questions']: output = list(set(output) | set(question['tags']))
|
||||||
|
return output
|
@ -34,3 +34,12 @@ def value(min:int=0, max:int=None):
|
|||||||
if value < min or max != None and value > max:
|
if value < min or max != None and value > max:
|
||||||
raise ValidationError(message)
|
raise ValidationError(message)
|
||||||
return length
|
return length
|
||||||
|
|
||||||
|
def get_time_options():
|
||||||
|
time_options = [
|
||||||
|
('none', 'None'),
|
||||||
|
('60', '1 hour'),
|
||||||
|
('90', '1 hour 30 minutes'),
|
||||||
|
('120', '2 hours')
|
||||||
|
]
|
||||||
|
return time_options
|
@ -1,2 +1,104 @@
|
|||||||
|
from .data import randomise_list
|
||||||
|
|
||||||
def parse_test_code(code):
|
def parse_test_code(code):
|
||||||
return code.replace('—', '').lower()
|
return code.replace('—', '').lower()
|
||||||
|
|
||||||
|
def generate_questions(dataset:list):
|
||||||
|
output = []
|
||||||
|
for block in randomise_list(dataset):
|
||||||
|
if block['type'] == 'question':
|
||||||
|
question = {
|
||||||
|
'type': 'question',
|
||||||
|
'q_no': block['q_no'],
|
||||||
|
'question_header': '',
|
||||||
|
'text': block['text']
|
||||||
|
}
|
||||||
|
if block['q_type'] == 'Multiple Choice': question['options'] = randomise_list([*enumerate(block['options'])])
|
||||||
|
else: question['options'] = block['options'].copy()
|
||||||
|
output.append(question)
|
||||||
|
elif block['type'] == 'block':
|
||||||
|
for key, _question in enumerate(randomise_list(block['questions'])):
|
||||||
|
question = {
|
||||||
|
'type': 'block',
|
||||||
|
'q_no': _question['q_no'],
|
||||||
|
'question_header': block['question_header'] if 'question_header' in block else '',
|
||||||
|
'block_length': len(block['questions']),
|
||||||
|
'block_q_no': key,
|
||||||
|
'text': _question['text']
|
||||||
|
}
|
||||||
|
if _question['q_type'] == 'Multiple Choice': question['options'] = randomise_list([*enumerate(_question['options'])])
|
||||||
|
else: question['options'] = _question['options'].copy()
|
||||||
|
output.append(question)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def evaluate_answers(answers:dict, key:list):
|
||||||
|
score = 0
|
||||||
|
max = 0
|
||||||
|
tags = {}
|
||||||
|
for block in key:
|
||||||
|
if block['type'] == 'question':
|
||||||
|
max += 1
|
||||||
|
q_no = block['q_no']
|
||||||
|
if str(q_no) in answers:
|
||||||
|
submitted_answer = int(answers[str(q_no)])
|
||||||
|
if submitted_answer == block['correct']:
|
||||||
|
score += 1
|
||||||
|
for tag in block['tags']:
|
||||||
|
if tag not in tags:
|
||||||
|
tags[tag] = {
|
||||||
|
'scored': 1,
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
tags[tag]['scored'] += 1
|
||||||
|
tags[tag]['max'] += 1
|
||||||
|
else:
|
||||||
|
for tag in block['tags']:
|
||||||
|
if tag not in tags:
|
||||||
|
tags[tag] = {
|
||||||
|
'scored': 0,
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
else: tags[tag]['max'] += 1
|
||||||
|
elif block['type'] == 'block':
|
||||||
|
for question in block['questions']:
|
||||||
|
max += 1
|
||||||
|
q_no = question['q_no']
|
||||||
|
if str(q_no) in answers:
|
||||||
|
submitted_answer = int(answers[str(q_no)])
|
||||||
|
if submitted_answer == question['correct']:
|
||||||
|
score += 1
|
||||||
|
for tag in question['tags']:
|
||||||
|
if tag not in tags:
|
||||||
|
tags[tag] = {
|
||||||
|
'scored': 1,
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
tags[tag]['scored'] += 1
|
||||||
|
tags[tag]['max'] += 1
|
||||||
|
else:
|
||||||
|
for tag in question['tags']:
|
||||||
|
if tag not in tags:
|
||||||
|
tags[tag] = {
|
||||||
|
'scored': 0,
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
else: tags[tag]['max'] += 1
|
||||||
|
grade = 'merit' if score/max >= .85 else 'pass' if score/max >= .70 else 'fail'
|
||||||
|
return {
|
||||||
|
'grade': grade,
|
||||||
|
'tags': tags,
|
||||||
|
'score': score,
|
||||||
|
'max': max
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_correct_answers(dataset:list):
|
||||||
|
output = {}
|
||||||
|
for block in dataset:
|
||||||
|
if block['type'] == 'question':
|
||||||
|
output[str(block['q_no'])] = block['options'][block['correct']]
|
||||||
|
if block['type'] == 'block':
|
||||||
|
for question in block['questions']:
|
||||||
|
output[str(question['q_no'])] = question['options'][question['correct']]
|
||||||
|
return output
|
12
ref-test/app/views.py
Normal file
12
ref-test/app/views.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from flask import Blueprint, render_template
|
||||||
|
|
||||||
|
views = Blueprint(
|
||||||
|
name='common',
|
||||||
|
import_name=__name__,
|
||||||
|
template_folder='templates',
|
||||||
|
static_folder='static'
|
||||||
|
)
|
||||||
|
|
||||||
|
@views.route('/privacy/')
|
||||||
|
def _privacy():
|
||||||
|
return render_template('privacy.html')
|
@ -1,10 +0,0 @@
|
|||||||
from flask import Blueprint
|
|
||||||
|
|
||||||
views = Blueprint(
|
|
||||||
name='common',
|
|
||||||
import_name=__name__,
|
|
||||||
template_folder='templates',
|
|
||||||
static_folder='static'
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import privacy
|
|
@ -1,5 +0,0 @@
|
|||||||
from . import views
|
|
||||||
|
|
||||||
@views.route('/privacy/')
|
|
||||||
def _privacy():
|
|
||||||
return 'Privacy Policy'
|
|
Loading…
Reference in New Issue
Block a user