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

CRLF -> LF; BASE_PARAMS and LIST PARAMS

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