Verified Commit 1f81087f authored by Peter Stanko's avatar Peter Stanko
Browse files

CRLF -> LF; BASE_PARAMS and LIST PARAMS

parent 8af23ea7
.idea/ .idea/
*.pyc *.pyc
__pycache__/ __pycache__/
.coverage .coverage
.pytest_cache/ .pytest_cache/
portal.local.cfg portal.local.cfg
devel.db devel.db
log/*.* log/*.*
/.env /.env
*.tmp *.tmp
from datetime import timedelta from datetime import timedelta
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from management.data.shared import DataFactory from management.data.shared import DataFactory
from portal.database.models import Review, Secret, SubmissionState from portal.database.models import Review, Secret, SubmissionState
from portal.tools import time from portal.tools import time
def init_test_data(app: Flask, db: SQLAlchemy): def init_test_data(app: Flask, db: SQLAlchemy):
with app.app_context(): with app.app_context():
factory = DataFactory(db) factory = DataFactory(db)
# users # users
student1 = factory.create_user(username='student1', uco=111) student1 = factory.create_user(username='student1', uco=111)
student2 = factory.create_user(username='student2', uco=222) student2 = factory.create_user(username='student2', uco=222)
teacher1 = factory.create_user(username='teacher1', password='123123', teacher1 = factory.create_user(username='teacher1', password='123123',
name='Teacher And Student', uco=1001) name='Teacher And Student', uco=1001)
teacher2 = factory.create_user(username='teacher2', password='123123', teacher2 = factory.create_user(username='teacher2', password='123123',
name='Teacher Only', uco=1002) name='Teacher Only', uco=1002)
teacher1.secrets.append(Secret('teacher1_secret', 'ultimate_teacher_secret')) teacher1.secrets.append(Secret('teacher1_secret', 'ultimate_teacher_secret'))
lecturer1 = factory.create_user(username='lecturer1', name='Courses Owner', uco=1010) lecturer1 = factory.create_user(username='lecturer1', name='Courses Owner', uco=1010)
# courses # courses
test_course1 = factory.create_course(codename='testcourse1', name='Test Course One', test_course1 = factory.create_course(codename='testcourse1', name='Test Course One',
faculty_id=100) faculty_id=100)
test_course2 = factory.create_course(codename='testcourse2', name='test Course Two', test_course2 = factory.create_course(codename='testcourse2', name='test Course Two',
faculty_id=100) faculty_id=100)
# groups # groups
tc1_students = factory.create_group(course=test_course1, name="seminar01") tc1_students = factory.create_group(course=test_course1, name="seminar01")
tc1_teachers = factory.create_group(course=test_course1, name="teachers") tc1_teachers = factory.create_group(course=test_course1, name="teachers")
tc2_students = factory.create_group(course=test_course2, name="seminar01") tc2_students = factory.create_group(course=test_course2, name="seminar01")
tc2_teachers = factory.create_group(course=test_course2, name="teachers") tc2_teachers = factory.create_group(course=test_course2, name="teachers")
tc1_hw01 = factory.scaffold_project(test_course1, 'hw01') tc1_hw01 = factory.scaffold_project(test_course1, 'hw01')
tc1_hw02 = factory.scaffold_project(test_course1, 'hw02') tc1_hw02 = factory.scaffold_project(test_course1, 'hw02')
tc2_hw01 = factory.scaffold_project(test_course2, 'hw01') tc2_hw01 = factory.scaffold_project(test_course2, 'hw01')
tc2_hw01 = factory.scaffold_project(test_course2, 'hw02') tc2_hw01 = factory.scaffold_project(test_course2, 'hw02')
# roles # roles
tc1_student = factory.scaffold_role(role_type='student', course=test_course1) tc1_student = factory.scaffold_role(role_type='student', course=test_course1)
tc1_teacher = factory.scaffold_role(role_type='teacher', course=test_course1) tc1_teacher = factory.scaffold_role(role_type='teacher', course=test_course1)
tc1_owner = factory.scaffold_role(role_type='owner', course=test_course1) tc1_owner = factory.scaffold_role(role_type='owner', course=test_course1)
tc1_workers = factory.scaffold_role(role_type='worker', course=test_course1) tc1_workers = factory.scaffold_role(role_type='worker', course=test_course1)
tc2_student = factory.scaffold_role(role_type='student', course=test_course2) tc2_student = factory.scaffold_role(role_type='student', course=test_course2)
tc2_teacher = factory.scaffold_role(role_type='teacher', course=test_course2) tc2_teacher = factory.scaffold_role(role_type='teacher', course=test_course2)
tc2_owner = factory.scaffold_role(role_type='owner', course=test_course2) tc2_owner = factory.scaffold_role(role_type='owner', course=test_course2)
tc2_workers = factory.scaffold_role(role_type='worker', course=test_course2) tc2_workers = factory.scaffold_role(role_type='worker', course=test_course2)
# relationships # relationships
teacher_student = teacher1 teacher_student = teacher1
teacher_both = teacher2 teacher_both = teacher2
student_tc1 = student1 student_tc1 = student1
student_both = student2 student_both = student2
# roles # roles
lecturer1.roles.append(tc1_owner) lecturer1.roles.append(tc1_owner)
lecturer1.roles.append(tc2_owner) lecturer1.roles.append(tc2_owner)
teacher_student.roles.append(tc1_teacher) teacher_student.roles.append(tc1_teacher)
teacher_student.roles.append(tc2_student) teacher_student.roles.append(tc2_student)
teacher_both.roles.append(tc1_teacher) teacher_both.roles.append(tc1_teacher)
teacher_both.roles.append(tc2_teacher) teacher_both.roles.append(tc2_teacher)
student_tc1.roles.append(tc1_student) student_tc1.roles.append(tc1_student)
student_both.roles.append(tc1_student) student_both.roles.append(tc1_student)
student_both.roles.append(tc2_student) student_both.roles.append(tc2_student)
# groups # groups
teacher_student.groups.append(tc1_teachers) teacher_student.groups.append(tc1_teachers)
teacher_student.groups.append(tc2_students) teacher_student.groups.append(tc2_students)
teacher_both.groups.append(tc1_teachers) teacher_both.groups.append(tc1_teachers)
teacher_both.groups.append(tc2_teachers) teacher_both.groups.append(tc2_teachers)
student_tc1.groups.append(tc1_students) student_tc1.groups.append(tc1_students)
student_both.groups.append(tc1_students) student_both.groups.append(tc1_students)
student_both.groups.append(tc2_students) student_both.groups.append(tc2_students)
# submissions # submissions
tc2_sub1 = factory.create_submission(user=teacher_student, project=tc2_hw01, tc2_sub1 = factory.create_submission(user=teacher_student, project=tc2_hw01,
parameters={}) parameters={})
tc2_sub1.state = SubmissionState.FINISHED tc2_sub1.state = SubmissionState.FINISHED
tc2_sub2 = factory.create_submission(user=student_both, project=tc2_hw01, tc2_sub2 = factory.create_submission(user=student_both, project=tc2_hw01,
parameters={}) parameters={})
tc2_sub2.state = SubmissionState.ABORTED tc2_sub2.state = SubmissionState.ABORTED
tc1_sub_p1_cancel = factory.create_submission(user=student_both, project=tc1_hw01, tc1_sub_p1_cancel = factory.create_submission(user=student_both, project=tc1_hw01,
parameters={}) parameters={})
tc1_sub_p1_cancel.state = SubmissionState.CANCELLED tc1_sub_p1_cancel.state = SubmissionState.CANCELLED
tc1_sub_p1_abort = factory.create_submission(user=student_tc1, project=tc1_hw01, tc1_sub_p1_abort = factory.create_submission(user=student_tc1, project=tc1_hw01,
parameters={}) parameters={})
tc1_sub_p1_abort.state = SubmissionState.ABORTED tc1_sub_p1_abort.state = SubmissionState.ABORTED
tc1_sub_p1_finished = factory.create_submission(user=student_both, project=tc1_hw01, tc1_sub_p1_finished = factory.create_submission(user=student_both, project=tc1_hw01,
parameters={}) parameters={})
tc1_sub_p1_finished.state = SubmissionState.FINISHED tc1_sub_p1_finished.state = SubmissionState.FINISHED
tc1_sub_p1_in_progress = factory.create_submission(user=student_tc1, project=tc1_hw01, tc1_sub_p1_in_progress = factory.create_submission(user=student_tc1, project=tc1_hw01,
parameters={}) parameters={})
tc1_sub_p1_in_progress.state = SubmissionState.IN_PROGRESS tc1_sub_p1_in_progress.state = SubmissionState.IN_PROGRESS
# Projects in groups # Projects in groups
tc1_students.projects.append(tc1_hw01) tc1_students.projects.append(tc1_hw01)
tc2_students.projects.append(tc2_hw01) tc2_students.projects.append(tc2_hw01)
db.session.add_all( db.session.add_all(
[tc2_sub1, tc2_sub2, tc1_sub_p1_cancel, tc1_sub_p1_abort, tc1_sub_p1_finished, [tc2_sub1, tc2_sub2, tc1_sub_p1_cancel, tc1_sub_p1_abort, tc1_sub_p1_finished,
tc1_sub_p1_in_progress]) tc1_sub_p1_in_progress])
# reviews # reviews
review = Review(tc2_sub1) review = Review(tc2_sub1)
factory.create_review_item(author=teacher_both, review=review, line=1, factory.create_review_item(author=teacher_both, review=review, line=1,
content="problem here") content="problem here")
factory.create_review_item(author=teacher_both, review=review, line=5, content="good stuff") factory.create_review_item(author=teacher_both, review=review, line=5, content="good stuff")
factory.create_review_item(author=teacher_student, review=review, line=1, content="oops") factory.create_review_item(author=teacher_student, review=review, line=1, content="oops")
db.session.add_all([review]) db.session.add_all([review])
# workers # workers
executor = factory.create_worker(name='executor', url="foo/url") executor = factory.create_worker(name='executor', url="foo/url")
executor.secrets.append(Secret('executor_secret', "executor_secret", executor.secrets.append(Secret('executor_secret', "executor_secret",
time.current_time() + timedelta(hours=1))) time.current_time() + timedelta(hours=1)))
processing = factory.create_worker(name='processing', url="bar/url") processing = factory.create_worker(name='processing', url="bar/url")
processing.secrets.append(Secret('processing_secret', "processing_secret", processing.secrets.append(Secret('processing_secret', "processing_secret",
time.current_time() + timedelta(hours=1))) time.current_time() + timedelta(hours=1)))
executor.roles.append(tc1_workers) executor.roles.append(tc1_workers)
processing.roles.append(tc2_workers) processing.roles.append(tc2_workers)
db.session.add_all([executor, processing]) db.session.add_all([executor, processing])
# Commit to the DB # Commit to the DB
db.session.commit() db.session.commit()
""" """
Database layer module Database layer module
""" """
from .models import User, Group, Project, ProjectState, ProjectConfig, Role, Course, Worker,\ from .models import User, Group, Project, ProjectState, ProjectConfig, Role, Course, Worker,\
Submission, SubmissionState, Review, ReviewItem Submission, SubmissionState, Review, ReviewItem
from .exceptions import PortalDbError from .exceptions import PortalDbError
""" """
Database specific exceptions Database specific exceptions
""" """
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
class PortalDbError(SQLAlchemyError): class PortalDbError(SQLAlchemyError):
"""Raised when a forbidden operation on portal's database is attempted. """Raised when a forbidden operation on portal's database is attempted.
""" """
""" """
A collection of Mixins specifying common behaviour and attributes of database entities. A collection of Mixins specifying common behaviour and attributes of database entities.
""" """
import datetime from typing import List
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from portal import db from portal import db
# maybe use server_default, server_onupdate instead of default, onupdate; crashes tests # maybe use server_default, server_onupdate instead of default, onupdate; crashes tests
# (https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime) # (https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime)
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
from portal.tools.naming import sanitize_code_name from portal.tools.meta import bound_update_class_var
from portal.tools.naming import sanitize_code_name
class EntityBase(object):
"""Entity mixin for the models def _repr(instance) -> str:
Attributes: """Repr helper function
created_at(Column): Date when the entity has been created Args:
updated_at(Column): Date when the entity has been updated instance: Instance of the model
"""
EXCLUDED = [] Returns(str): String representation of the model
created_at = db.Column(db.TIMESTAMP, default=db.func.now()) """
updated_at = db.Column( if isinstance(instance, EntityBase):
db.TIMESTAMP, default=db.func.now(), onupdate=db.func.now()) params = {key: getattr(instance, key) for key in instance.base_params()}
return str(params)
return instance.__dict__
class NamedMixin(object):
_name = db.Column('name', db.String(50), nullable=False)
_codename = db.Column('codename', db.String(30), nullable=False) def _str(instance) -> str:
description = db.Column(db.Text) if hasattr(instance, 'log_name'):
return instance.log_name
@hybrid_property return _repr(instance=instance)
def codename(self):
return self._codename
class EntityBase:
@codename.setter """Entity mixin for the models
def codename(self, value: str):
"""Sets codename of the entity only the value is provided Class attributes:
created_at(Column): Date when the entity has been created
Codename setter will set codename only if value is provided. updated_at(Column): Date when the entity has been updated
It will also set the name to the value, if the name is empty (None) """
Codename has to be sanitized - it means that it is slugyfied and shortened BASE_PARAMS = ['created_at', 'updated_at']
LISTABLE = []
Args: UPDATABLE = []
value: Codename of the entity
""" @classmethod
if value: def base_params(cls) -> List[str]:
self._codename = sanitize_code_name(value) return bound_update_class_var(cls, 'BASE_PARAMS')
if self._name is None:
self._name = self._codename @classmethod
def updatable_params(cls) -> List[str]:
@hybrid_property return bound_update_class_var(cls, 'UPDATABLE')
def name(self) -> str:
"""Gets name @classmethod
Returns (str): Name of the entity def listable_params(cls) -> List[str]:
return bound_update_class_var(cls, 'LISTABLE')
"""
return self._name created_at = db.Column(db.TIMESTAMP, default=db.func.now())
updated_at = db.Column(db.TIMESTAMP, default=db.func.now(), onupdate=db.func.now())
@name.setter
def name(self, value): def __repr__(self):
"""Sets the name of the entity return _repr(self)
Args:
value(str): Name of the entity def __str__(self):
""" return _str(self)
if self._codename is None:
self.codename = value
self._name = value class NamedMixin:
# pylint: enable=too-few-public-methods LISTABLE = ['name', 'codename']
UPDATABLE = [*LISTABLE, 'description']
BASE_PARAMS = [*UPDATABLE]
_name = db.Column('name', db.String(50), nullable=False)
_codename = db.Column('codename', db.String(30), nullable=False)
description = db.Column(db.Text)
@hybrid_property
def codename(self):
return self._codename
@codename.setter
def codename(self, value: str):
"""Sets codename of the entity only the value is provided
Codename setter will set codename only if value is provided.
It will also set the name to the value, if the name is empty (None)
Codename has to be sanitized - it means that it is slugyfied and shortened
Args:
value: Codename of the entity
"""
if value:
self._codename = sanitize_code_name(value)
if self._name is None:
self._name = self._codename
@hybrid_property
def name(self) -> str:
"""Gets name
Returns (str): Name of the entity
"""
return self._name
@name.setter
def name(self, value):
"""Sets the name of the entity
Args:
value(str): Name of the entity
"""
if self._codename is None:
self.codename = value
self._name = value
# pylint: enable=too-few-public-methods
...@@ -7,10 +7,12 @@ import logging ...@@ -7,10 +7,12 @@ import logging
import uuid import uuid
from typing import List from typing import List
import sqlalchemy as sa
from flask_sqlalchemy import BaseQuery from flask_sqlalchemy import BaseQuery
from sqlalchemy import event from sqlalchemy import event
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy_continuum import make_versioned
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
from portal import db from portal import db
...@@ -19,34 +21,11 @@ from portal.database.mixins import EntityBase, NamedMixin ...@@ -19,34 +21,11 @@ from portal.database.mixins import EntityBase, NamedMixin
from portal.database.types import JSONEncodedDict, YAMLEncodedDict from portal.database.types import JSONEncodedDict, YAMLEncodedDict
from portal.tools import time from portal.tools import time
from portal.tools.time import normalize_time from portal.tools.time import normalize_time
from sqlalchemy_continuum import make_versioned
import sqlalchemy as sa
make_versioned(user_cls=None) make_versioned(user_cls=None)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def _repr(instance) -> str:
"""Repr helper function
Args:
instance: Instance of the model
Returns(str): String representation of the model
"""
no_include = {'password_hash', 'review', 'course', 'user', 'project', 'group', 'role',
'review_items', 'client', 'EXCLUDED', 'query', 'groups', 'roles', 'secrets',
'courses', 'projects'}
if 'EXCLUDED' in vars(instance.__class__):
no_include.update(instance.__class__.EXCLUDED)
result = f"{instance.__class__.__name__}: "
for key in dir(instance):
if not key.startswith("_") and key not in no_include:
value = getattr(instance, key)
if not callable(value):
result += f"{key}={value} "
return result
class ClientType(enum.Enum): class ClientType(enum.Enum):
"""All known client types """All known client types
""" """
...@@ -54,12 +33,13 @@ class ClientType(enum.Enum): ...@@ -54,12 +33,13 @@ class ClientType(enum.Enum):
WORKER = 'worker' WORKER = 'worker'
class Client(db.Model): class Client(db.Model, EntityBase):
"""Client entity model """Client entity model
""" """
__tablename__ = 'client' __tablename__ = 'client'
id = db.Column(db.String(length=36), default=lambda: str( LISTABLE = ['id', 'type', 'codename']
uuid.uuid4()), primary_key=True) UPDATABLE = ['codename']
id = db.Column(db.String(length=36), default=lambda: str(uuid.uuid4()), primary_key=True)
type = db.Column(db.Enum(ClientType, name='ClientType'), nullable=False) type = db.Column(db.Enum(ClientType, name='ClientType'), nullable=False)
codename = db.Column(db.String(30), unique=True, nullable=False) codename = db.Column(db.String(30), unique=True, nullable=False)
secrets = db.relationship('Secret', back_populates='client', uselist=True, secrets = db.relationship('Secret', back_populates='client', uselist=True,
...@@ -141,9 +121,10 @@ class Client(db.Model): ...@@ -141,9 +121,10 @@ class Client(db.Model):
class Secret(db.Model, EntityBase): class Secret(db.Model, EntityBase):
__tablename__ = 'secret' __tablename__ = 'secret'
EXCLUDED = ['value', 'client'] UPDATABLE = ['name', 'expires_at']
id = db.Column(db.String(length=36), default=lambda: str( BASE_PARAMS = ['id', *UPDATABLE]
uuid.uuid4()), primary_key=True)
id = db.Column(db.String(length=36), default=lambda: str(uuid.uuid4()), primary_key=True)
name = db.Column(db.String(40), nullable=False) name = db.Column(db.String(40), nullable=False)
value = db.Column(db.String(120)) value = db.Column(db.String(120))
expires_at = db.Column(db.TIMESTAMP, nullable=True) expires_at = db.Column(db.TIMESTAMP, nullable=True)
...@@ -176,7 +157,7 @@ class Secret(db.Model, EntityBase): ...@@ -176,7 +157,7 @@ class Secret(db.Model, EntityBase):
return time.normalize_time(self.expires_at) < time.current_time() return time.normalize_time(self.expires_at) < time.current_time()
class User(EntityBase, Client): class User(Client):
"""User entity model """User entity model
Attributes: Attributes:
...@@ -192,10 +173,11 @@ class User(EntityBase, Client): ...@@ -192,10 +173,11 @@ class User(EntityBase, Client):
review_items: Collection of review_items created by user review_items: Collection of review_items created by user
groups: Collection of groups the user belongs to groups: Collection of groups the user belongs to
""" """
EXCLUDED = ['password_hash', 'submissions', 'review_items'] UPDATABLE = ['name', 'email', 'uco', 'gitlab_username', 'managed']
BASE_PARAMS = ['username', 'codename', 'is_admin', *UPDATABLE, 'managed', 'id']
__tablename__ = 'user' __tablename__ = 'user'
id = db.Column(db.String(length=36), db.ForeignKey('client.id'), default=