class StatementProblem(db.Model): __table_args__ = {'schema': 'moodle'} __tablename__ = 'mdl_statements_problems_correlation' id = db.Column(db.Integer, primary_key=True) statement_id = db.Column(db.Integer, db.ForeignKey('moodle.mdl_statements.id')) problem_id = db.Column(db.Integer, db.ForeignKey('moodle.mdl_problems.id')) rank = db.Column('rank', db.Integer) hidden = db.Column('hidden', db.Integer) statement = db.relationship( 'Statement', backref=db.backref( 'StatementProblems', collection_class=attribute_mapped_collection("rank"))) # reference to the "Keyword" object problem = db.relationship('Problem', backref=db.backref('StatementProblems')) def __init__(self, statement_id, problem_id, rank): self.statement_id = statement_id self.problem_id = problem_id self.rank = rank self.hidden = 0
class CourseModule(db.Model): __table_args__ = {'schema': 'moodle'} __tablename__ = 'mdl_course_modules' id = db.Column(db.Integer, primary_key=True) course_id = db.Column('course', db.Integer, db.ForeignKey('moodle.mdl_course.id')) module = db.Column(db.Integer) instance_id = db.Column('instance', db.Integer) section_id = db.Column('section', db.Integer, db.ForeignKey('moodle.mdl_course_sections.id')) visible = db.Column(db.Boolean) course = db.relationship('Course', backref=db.backref('course_modules', lazy='dynamic')) section = db.relationship('CourseSection', backref=db.backref('modules', lazy='dynamic')) @property def instance(self) -> Type['CourseModuleInstance']: if not hasattr(self, '_instance'): instance_class = next( ( subclass for subclass in CourseModuleInstance.__subclasses__() if subclass.MODULE == self.module ), None ) if not instance_class: self._instance = None else: self._instance = db.session.query(instance_class) \ .filter_by(id=self.instance_id) \ .first() return self._instance @deprecated(' `Do not use serialize inside model!` ') def serialize(self): serialized = attrs_to_dict( self, 'id', 'course_id', 'module', 'section_id', 'visible', ) if self.instance: serialized['type'] = self.instance.MODULE_TYPE if self.instance.MODULE_TYPE == 'STATEMENT': serialized['instance'] = attrs_to_dict( self.instance, 'id', 'name', ) elif self.instance.MODULE_TYPE in ['BOOK', 'MONITOR']: serialized['instance'] = self.instance.serialize(course_module_id=self.id) else: serialized['instance'] = self.instance.serialize() return serialized
class WorkshopMonitor(db.Model): __table_args__ = {'schema': 'pynformatics'} __tablename__ = 'contest_monitor' id = db.Column(db.Integer, primary_key=True) workshop_id = db.Column(db.Integer, db.ForeignKey('pynformatics.workshop.id')) type = db.Column(IntEnum(WorkshopMonitorType), default=WorkshopMonitorType.IOI, nullable=False) user_visibility = db.Column(IntEnum(WorkshopMonitorUserVisibility), default=WorkshopMonitorUserVisibility.FULL, nullable=False) with_penalty_time = db.Column(db.Boolean, default=False) freeze_time = db.Column(db.DateTime) workshop = db.relationship('WorkShop', backref=db.backref( 'monitors', cascade='all, delete-orphan')) def is_for_user_only(self): return self.user_visibility == WorkshopMonitorUserVisibility.FOR_USER_ONLY def is_disabled_for_students(self): return self.user_visibility == WorkshopMonitorUserVisibility.DISABLED_FOR_STUDENT
class User(SimpleUser): __mapper_args__ = {'polymorphic_identity': 'user'} username = db.Column(db.Unicode(100)) email = db.Column(db.Unicode(100)) city = db.Column(db.Unicode(20)) school = db.Column(db.Unicode(255)) problems_week_solved = db.Column(db.Integer) roles = db.relationship('Role', secondary='moodle.mdl_role_assignments', lazy='select') @property def token(self): return generate_auth_token(self) @classmethod def check_password(cls, password_md5: str, password: str) -> bool: return password_md5 == cls.hash_password(password) @staticmethod def hash_password(plan_password: str) -> str: """Returns MD5 hash for plan password string. Raises ValueError if password string is invalid. :param plan_password: password string to hash :return: hashed password """ return hashlib.md5(plan_password.encode('utf-8')).hexdigest()
class Statement(db.Model): __table_args__ = {'schema': 'moodle'} __tablename__ = 'mdl_statements' __mapper_args__ = { 'polymorphic_identity': 'statement', 'concrete': True, } id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Unicode(255)) summary = db.Column(MEDIUMTEXT) numbering = db.Column(db.Integer) disable_printing = db.Column('disableprinting', db.Boolean) custom_titles = db.Column('customtitles', db.Boolean) time_created = db.Column('timecreated', db.Integer) time_modified = db.Column('timemodified', db.Integer) contest_id = db.Column(db.Integer) time_start = db.Column('timestart', db.Integer) time_stop = db.Column('timestop', db.Integer) olympiad = db.Column(db.Boolean) virtual_olympiad = db.Column(db.Boolean) virtual_duration = db.Column(db.Integer) settings = db.Column(JsonType) problems = db.relationship('Problem', secondary='moodle.mdl_statements_problems_correlation')
def course_module(cls): class_name = cls.__name__ class_module = cls.MODULE return db.relationship( CourseModule, primaryjoin=f'and_({class_name}.id==CourseModule.instance_id,' f'CourseModule.module=={class_module})', foreign_keys='%s.id' % cls.__name__, )
class StatementStandings(StandingsMixin, db.Model): __tablename__ = 'statement_standings' statement_id = db.Column(db.Integer, db.ForeignKey('moodle.mdl_statements.id'), primary_key=True) statement = db.relationship('Statement', backref=db.backref('standings', uselist=False, lazy='joined'))
class WorkshopConnection(db.Model): __table_args__ = ( db.UniqueConstraint('user_id', 'workshop_id', name='_workshop_user_uc'), {'schema': 'pynformatics'}, ) __tablename__ = 'workshop_connection' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.ForeignKey('moodle.mdl_user.id'), index=True) workshop_id = db.Column( db.Integer, db.ForeignKey('pynformatics.workshop.id', ondelete='CASCADE', name='fk_course_id'), nullable=False ) status = db.Column( IntEnum(WorkshopConnectionStatus), default=WorkshopConnectionStatus.APPLIED, # TODO: возможно, вернуть на ACCEPTED nullable=False ) user = db.relationship('User') workshop = db.relationship( 'WorkShop', backref=db.backref('connections', cascade='all, delete-orphan') ) def is_accepted(self): return self.status == WorkshopConnectionStatus.ACCEPTED def is_promoted(self): return self.status == WorkshopConnectionStatus.PROMOTED def is_avialable(self): return self.is_accepted() or self.is_promoted() def __repr__(self): return ( f'<WorkshopConnection ' f'id="{self.id}" ' f'workshop_id="{self.workshop_id}" ' f'user_id="{self.user_id}">' )
class ProblemStandings(StandingsMixin, db.Model): __tablename__ = 'problem_standings' __table_args__ = {'schema': 'pynformatics'} problem_id = db.Column(db.Integer, db.ForeignKey('moodle.mdl_problems.id'), primary_key=True) problem = db.relationship('EjudgeProblem', backref=db.backref('standings', uselist=False, lazy='joined'))
class WorkShop(db.Model): __table_args__ = {'schema': 'pynformatics'} __tablename__ = 'workshop' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), nullable=False, default='Время Сборов.') status = db.Column(IntEnum(WorkshopStatus), nullable=False, default=WorkshopStatus.DRAFT) visibility = db.Column(IntEnum(WorkshopVisibility), nullable=False, default=WorkshopVisibility.PRIVATE) access_token = db.Column(db.String(32), nullable=False) contests = db.relationship('Contest', back_populates='workshop')
class CourseSection(db.Model): __table_args__ = {'schema': 'moodle'} __tablename__ = 'mdl_course_sections' id = db.Column(db.Integer, primary_key=True) course_id = db.Column('course', db.Integer, db.ForeignKey('moodle.mdl_course.id')) section = db.Column(db.Integer) summary = db.Column(db.Text) sequence_text = db.Column('sequence', db.Text) visible = db.Column(db.Boolean) course = db.relationship('Course', backref=db.backref( 'sections', lazy='dynamic', order_by='CourseSection.section')) def __init__(self): self._sequence = None @property def sequence(self): if not getattr(self, '_sequence'): try: self._sequence = list(map(int, self.sequence_text.split(','))) except Exception: self._sequence = [] return self._sequence @sequence.setter def sequence(self, value): self._sequence = value self.sequence_text = ','.join(list(map(str, value))) def serialize(self): serialized = attrs_to_dict( self, 'id', 'course_id', 'section', 'summary', 'sequence', 'visible', ) serialized['modules'] = [ module.serialize() for module in self.modules.filter_by(visible=True).all() ] return serialized
class RefreshToken(db.Model): __tablename__ = 'refresh_token' __table_args__ = (db.Index('search_token', 'token'), { 'schema': 'pynformatics' }) id = db.Column(db.Integer(), primary_key=True) token = db.Column(db.String(255)) user_id = db.Column(db.Integer(), db.ForeignKey(User.id)) valid = db.Column(db.Boolean(), default=True, nullable=False) created_at = db.Column(db.DateTime(), default=datetime.datetime.utcnow) user = db.relationship('User', lazy='joined', backref=backref('refresh_tokens', cascade="all, delete-orphan"), single_parent=True)
class SimpleUser(db.Model): RESET_PASSWORD_LENGTH = 20 __table_args__ = ({'schema': 'moodle'}) __tablename__ = 'mdl_user' id = db.Column(db.Integer, primary_key=True) firstname = db.Column(db.Unicode(100)) lastname = db.Column(db.Unicode(100)) deleted = db.Column('deleted', db.Boolean) problems_solved = db.Column(db.Integer) password_md5 = db.Column('password', db.Unicode(32)) statement = db.relationship( 'Statement', secondary=StatementUser.__table__, backref='StatementUsers1', lazy='dynamic', ) statements = association_proxy('StatementUsers2', 'statement')
class ContestConnection(db.Model): __table_args__ = ( db.UniqueConstraint('user_id', 'contest_id', name='_contest_user_uc'), { 'schema': 'pynformatics' }, ) __tablename__ = 'contest_connection' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer) contest_id = db.Column( db.Integer, db.ForeignKey('pynformatics.contest.id', ondelete='CASCADE'), ) created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow) contest = db.relationship('Contest', backref=db.backref('connections', cascade='all, delete-orphan'))
class User(SimpleUser): __mapper_args__ = {'polymorphic_identity': 'user'} username = db.Column(db.Unicode(100)) email = db.Column(db.Unicode(100)) city = db.Column(db.Unicode(20)) school = db.Column(db.Unicode(255)) problems_week_solved = db.Column(db.Integer) roles = db.relationship('Role', secondary='moodle.mdl_role_assignments', lazy='select') @property def token(self): return generate_jwt_token(self) @staticmethod def check_password(password_md5: str, password: str) -> bool: hashed_password = hashlib.md5(password.encode('utf-8')).hexdigest() if password_md5 == hashed_password: return True return False
class Statement(CourseModuleInstance, db.Model): __table_args__ = {'schema': 'moodle'} __tablename__ = 'mdl_statements' __mapper_args__ = { 'polymorphic_identity': 'statement', 'concrete': True, } MODULE = 19 id = db.Column(db.Integer, primary_key=True) course_id = db.Column('course', db.Integer, db.ForeignKey('moodle.mdl_course.id')) name = db.Column(db.Unicode(255)) summary = db.Column(MEDIUMTEXT) numbering = db.Column(db.Integer) disable_printing = db.Column('disableprinting', db.Boolean) custom_titles = db.Column('customtitles', db.Boolean) time_created = db.Column('timecreated', db.Integer) time_modified = db.Column('timemodified', db.Integer) contest_id = db.Column(db.Integer) time_start = db.Column('timestart', db.Integer) time_stop = db.Column('timestop', db.Integer) olympiad = db.Column(db.Boolean) virtual_olympiad = db.Column(db.Boolean) virtual_duration = db.Column(db.Integer) settings = db.Column(JsonType) course = db.relationship('Course', backref=db.backref('statements', lazy='dynamic')) user = association_proxy('StatementUsers1', 'user') SETTINGS_SCHEMA = { 'type': 'object', 'properties': { 'allowed_languages': { 'type': 'array', 'uniqueItems': True, 'items': { 'type': 'integer', 'enum': list(LANG_NAME_BY_ID.keys()), } }, 'type': { 'oneOf': [{ 'type': 'null', }, { 'type': 'string', 'enum': [ 'olympiad', 'virtual', ], }], }, 'group': { 'type': 'integer', }, 'team': { 'type': 'boolean', }, 'time_start': { 'type': 'integer', }, 'time_stop': { 'type': 'integer', }, 'freeze_time': { 'type': 'integer', }, 'standings': { 'type': 'boolean', }, 'test_only_samples': { 'type': 'boolean', }, 'reset_submits_on_start': { 'type': 'boolean', }, 'test_until_fail': { 'type': 'boolean', }, 'start_from_scratch': { 'type': 'boolean', }, 'restrict_view': { 'type': 'boolean', } }, 'additionalProperties': False, } SETTINGS_SCHEMA_VALIDATOR = Draft4Validator(SETTINGS_SCHEMA) def get_allowed_languages(self): if not (self.settings and 'allowed_languages' in self.settings): return None return self.settings['allowed_languages'] def set_settings(self, settings): validation_error = next( self.SETTINGS_SCHEMA_VALIDATOR.iter_errors(settings), None) if validation_error: raise ValueError(validation_error.message) self.settings = settings if settings.get('time_start'): self.time_start = settings['time_start'] if settings.get('time_stop'): self.time_stop = settings['time_stop'] if 'type' in settings: type_ = settings['type'] if type_ == None: self.olympiad = False self.virtual_olympiad = False elif type_ == 'olympiad': self.olympiad = True self.virtual_olympiad = False else: self.olympiad = False self.virtual_olympiad = True self.time_modified = int(time.time()) def start_participant( self, user, duration, password=None, ): if self.course \ and self.course.require_password() \ and password != self.course.password: raise ValueError() if self.participants.filter(Participant.user_id == user.id).count(): raise ValueError() if user.get_active_participant(): raise ValueError() new_participant = Participant( user_id=user.id, statement_id=self.id, start=int(time.time()), duration=duration, ) db.session.add(new_participant) return new_participant def finish_participant(self, user): active_participant = user.get_active_participant() if not active_participant or active_participant.statement_id != self.id: raise ValueError() active_participant.duration = int(time.time() - active_participant.start) return active_participant def start( self, user, password=None, ): if not self.olympiad: raise ValueError() now = time.time() if now < self.time_start: raise ValueError() if now >= self.time_stop: raise ValueError() return self.start_participant( user=user, duration=self.time_stop - int(time.time()), password=password, ) def finish(self, user): if not self.olympiad: raise ValueError() return self.finish_participant(user) def serialize(self, attributes=None): if not attributes: attributes = ( 'id', 'name', 'olympiad', 'settings', 'time_start', 'time_stop', 'virtual_olympiad', 'virtual_duration', 'course_module_id', 'course', 'require_password', ) serialized = attrs_to_dict(self, *attributes) if 'course' in attributes and self.course: serialized['course'] = self.course.serialize() serialized['course_module_id'] = getattr(self.course_module, 'id', None) if 'require_password' in attributes: if self.course: serialized['require_password'] = self.course.require_password() else: serialized['require_password'] = False user = getattr(g, 'user', None) if self.olympiad or self.virtual_olympiad: if not user: return serialized try: participant = self.participants.filter_by( user_id=user.id).one() except NoResultFound: return serialized serialized['participant'] = participant.serialize() serialized['problems'] = { rank: { 'id': statement_problem.problem.id, 'name': statement_problem.problem.name, } for rank, statement_problem in self.StatementProblems.items() if statement_problem.problem and not statement_problem.hidden } return serialized
class Contest(db.Model): __table_args__ = {'schema': 'pynformatics'} __tablename__ = 'contest' id = db.Column(db.Integer, primary_key=True) workshop_id = db.Column(db.Integer, db.ForeignKey('pynformatics.workshop.id')) statement_id = db.Column(db.Integer, db.ForeignKey('moodle.mdl_statements.id')) author_id = db.Column(db.Integer) position = db.Column(db.Integer, default=1) protocol_visibility = db.Column(IntEnum(ContestProtocolVisibility), default=ContestProtocolVisibility.FULL, server_default=str( ContestProtocolVisibility.FULL.value), nullable=False) is_virtual = db.Column(db.Boolean, default=False) time_start = db.Column(db.DateTime) time_stop = db.Column(db.DateTime) virtual_duration = db.Column(db.Interval, default=datetime.timedelta(seconds=0)) created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow) statement = db.relationship('Statement') workshop = db.relationship('WorkShop', back_populates='contests') languages = db.relationship('Language', secondary='pynformatics.language_contest') def _is_available_by_duration(self) -> bool: """ Checks date time restrictions """ current_time = datetime.datetime.utcnow() if self.time_start is not None and self.time_start > current_time: return False if self.time_stop is not None and self.time_stop < current_time: return False return True def _is_available_for_connection(self, cc: ContestConnection) -> bool: """ Checks if virtual contest is not expired for ContestConnection """ if not self.is_virtual: return True current_time = datetime.datetime.utcnow() if cc.created_at + self.virtual_duration < current_time: return False return True def is_available(self, cc) -> bool: return self._is_available_by_duration() and \ self._is_available_for_connection(cc) def is_started(self, cc: Optional[ContestConnection]) -> bool: current_time = datetime.datetime.utcnow() if not self.is_virtual: return current_time > self.time_start return bool(cc)