from ..extensions import db from ..tools.forms import JsonEncodedDict from ..tools.logs import write from flask_login import current_user from sqlalchemy.exc import SQLAlchemyError from datetime import date, datetime import secrets from uuid import uuid4 class Test(db.Model): id = db.Column(db.String(36), primary_key=True) code = db.Column(db.String(36), nullable=False) start_date = db.Column(db.DateTime, nullable=True) end_date = db.Column(db.DateTime, nullable=True) time_limit = db.Column(db.Integer, nullable=True) creator_id = db.Column(db.String(36), db.ForeignKey('user.id')) dataset_id = db.Column(db.String(36), db.ForeignKey('dataset.id')) adjustments = db.Column(JsonEncodedDict, nullable=True) entries = db.relationship('Entry', backref='test') def __repr__(self): return f'' @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): code = self.code.upper() return '—'.join([code[:4], code[4:8], code[8:]]) def create(self): self.generate_id() self.generate_code() self.creator = current_user errors = [] if self.start_date.date() < date.today(): errors.append('The start date cannot be in the past.') if self.end_date.date() < date.today(): errors.append('The expiry date cannot be in the past.') if self.end_date < self.start_date: errors.append('The expiry date cannot be before the start date.') if errors: return False, errors try: db.session.add(self) db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when creating test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Test with code {self.get_code()} created by {current_user.get_username()}.') return True, f'Test with code {self.get_code()} has been created.' def delete(self): if self.entries: return False, f'Cannot delete a test with submitted entries.' db.session.delete(self) try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when deleting test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Test with code {self.get_code()} has been deleted by {current_user.get_username()}.') return True, f'Test with code {self.get_code()} has been deleted.' def start(self): now = datetime.now() if self.start_date.date() > now.date(): self.start_date = now try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when launching test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Test with code {self.get_code()} has been started by {current_user.get_username()}.') return True, f'Test with code {self.get_code()} has been started.' return False, f'Test with code {self.get_code()} has already started.' def end(self): now = datetime.now() if self.end_date >= now: self.end_date = now try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when closing test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Test with code {self.get_code()} ended by {current_user.get_username()}.') return True, f'Test with code {self.get_code()} has been ended.' return False, f'Test with code {self.get_code()} has already ended.' def add_adjustment(self, time:int): adjustments = self.adjustments if self.adjustments is not None else {} code = secrets.token_hex(3).lower() adjustments[code] = time self.adjustments = adjustments try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when adding adjustment to test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Time adjustment for {time} minutes with code {code} added to test {self.get_code()} by {current_user.get_username()}.') return True, f'Time adjustment for {time} minutes added to test {self.get_code()}. This can be accessed using the user code {code.upper()}.' def remove_adjustment(self, code:str): if not self.adjustments: return False, f'There are no adjustments configured for test {self.get_code()}.' self.adjustments.pop(code) if not self.adjustments: self.adjustments = None try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when deleting adjustment from test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Time adjustment for with code {code} has been removed from test {self.get_code()} by {current_user.get_username()}.') return True, f'Time adjustment for with code {code} has been removed from test {self.get_code()}.' def update(self, start_date:datetime=None, end_date:datetime=None, time_limit:int=None): if not start_date and not end_date and time_limit is None: return False, 'There were no changes requested.' if start_date: self.start_date = start_date if end_date: self.end_date = end_date if time_limit is not None: self.time_limit = time_limit try: db.session.commit() except (SQLAlchemyError, ConnectionError) as exception: db.session.rollback() write('system.log', f'Database error when updating test {self.get_code()}: {exception}') return False, f'Database error: {exception}' write('system.log', f'Test with code {self.get_code()} has been updated by user {current_user.get_username()}.') return True, f'Test with code {self.get_code()} has been updated by.'