Commit 4401179e authored by Barbora Kompišová's avatar Barbora Kompišová
Browse files

service layer, submission creation refactor

parent 97c14810
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -13,6 +13,8 @@ pytest = "*"
flask-restful = "*"
flask-restful = "*"
marshmallow = "*"
marshmallow = "*"
flask-jwt-extended = "*"
flask-jwt-extended = "*"
marshmallow-enum = "*"
storage = { git="git@gitlab.fi.muni.cz:grp-kontr2/kontr-storage-module.git", editable='true'  }




[dev-packages]
[dev-packages]
+16 −1
Original line number Original line Diff line number Diff line
@@ -2,14 +2,25 @@ from flask import Flask
from portal.config import CONFIGURATIONS
from portal.config import CONFIGURATIONS
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
import os
import os
from storage.core.storage import Storage
from portal.rest.courses.courses import courses
from portal.rest.users.users import users
from portal.rest.roles.roles import roles
from portal.rest.groups.groups import groups


db = SQLAlchemy()
db = SQLAlchemy()
app = Flask('portal')
app = Flask('portal')
# TODO - urgent
storage = Storage({
    "submissions_dir": "todo",
    "test_files_dir": "todo",
    "workspace_dir": "todo"
})




def config_app(flask_app):
def config_app(flask_app):
    """Configures the Flask app based on production stage."""
    """Configures the Flask app based on production stage."""
    config_type = os.environ.get('PORTAL_CONFIG_TYPE', 'devel')  # TODO: meh name
    config_type = os.environ.get('PORTAL_CONFIG_TYPE', 'devel')
    config_object = CONFIGURATIONS[config_type]
    config_object = CONFIGURATIONS[config_type]
    if config_object is None:
    if config_object is None:
        raise EnvironmentError()  # TODO some custom ex
        raise EnvironmentError()  # TODO some custom ex
@@ -20,6 +31,10 @@ def config_app(flask_app):
def create_app():
def create_app():
    # app configuration
    # app configuration
    config_app(app)
    config_app(app)
    app.register_blueprint(courses)
    app.register_blueprint(users)
    app.register_blueprint(roles)
    app.register_blueprint(groups)
    # database bind to app
    # database bind to app
    db.init_app(app)
    db.init_app(app)


+4 −0
Original line number Original line Diff line number Diff line
@@ -315,6 +315,10 @@ class Submission(db.Model, EntityBase):
    review = db.relationship("Review", back_populates="submission", cascade="all, delete-orphan",
    review = db.relationship("Review", back_populates="submission", cascade="all, delete-orphan",
                             passive_deletes=True, uselist=False)
                             passive_deletes=True, uselist=False)


    def change_state(self, new_state):
        # open to extension (state transition validation, ...)
        self.state = new_state

    def __init__(self, user, project, parameters, review=None):
    def __init__(self, user, project, parameters, review=None):
        self.user = user
        self.user = user
        self.project = project
        self.project = project
+41 −111
Original line number Original line Diff line number Diff line
from marshmallow import Schema, fields
from marshmallow import Schema, fields, validates_schema, ValidationError
from portal import db
from marshmallow_enum import EnumField
from portal.database.models import Course, Role, User, Project, Group
from portal.tools.logging import log
# Maybe: extract tuples used in 'only' to variables (-> in one place)



from portal.database.models import SubmissionState
def delete_entity(entity):
    db.session.remove(entity)
    db.session.commit()


def write_entity(entity):
    db.session.add(entity)
    db.session.commit()


def find_course(identifier):
    log.debug(f"Attempting course search by id.")
    course = Course.query.filter_by(id=identifier).first()
    if course:
        return course

    log.debug(f"Did not find course with id={identifier}.")
    log.debug(f"Attempting course search by codename.")
    course = Course.query.filter_by(codename=identifier).first()
    if course:
        return course

    log.debug(f"Did not find course with codename={identifier}.")
    return None


def find_project(course, identifier):
    log.debug(f"Attempting project search by id.")
    project = Project.query.filter_by(course=course).filter_by(id=identifier).first()
    if project:
        return project

    log.debug(f"Did not find project with id={identifier}.")
    log.debug(f"Attempting project search by name.")
    project = Project.query.filter_by(course=course).filter_by(name=identifier).first()
    if project:
        return project

    log.debug(f"Did not find project with codename={identifier}.")
    return None


def find_group(course, identifier):
    log.debug(f"Attempting group search by id.")
    group = Group.query.filter_by(course=course).filter_by(id=identifier).first()
    if group:
        return group

    log.debug(f"Did not find group with id={identifier}.")
    log.debug(f"Attempting group search by name.")
    group = Group.query.filter_by(course=course).filter_by(name=identifier).first()
    if group:
        return group

    log.debug(f"Did not find group with name={identifier}.")
    return None


def find_role(course, identifier):
    log.debug(f"Attempting role search by id.")
    role = Role.query.filter_by(course=course).filter_by(id=identifier).first()
    if role:
        return role

    log.debug(f"Did not find role with id={identifier} in course {course.codename}.")
    log.debug(f"Attempting role search by name.")
    role = Role.query.filter_by(course=course).filter_by(name=identifier).first()
    if role:
        return role

    log.debug(f"Did not find role with name={identifier} in course {course.codename}.")
    return None


def find_user(identifier):
    log.debug(f"Attempting user search by id.")
    user = User.query.filter_by(id=identifier).first()
    if user:
        return user

    log.debug(f"Did not find user with id={identifier}.")
    log.debug(f"Attempting user search by email.")

    user = User.query.filter_by(email=identifier).first()
    if user:
        return user
    log.debug(f"Did not find user with email={identifier}.")
    log.debug(f"Attempting user search by username.")

    user = User.query.filter_by(username=identifier).first()
    if user:
        return user

    log.debug(f"Did not find user with username={identifier}.")
    # not searching by uco
    return None




class UserSchema(Schema):
class UserSchema(Schema):
    id = fields.Str(dump_only=True)  # TODO: check  if i need the ids here at all
    id = fields.Str(dump_only=True)
    uco = fields.Int()
    uco = fields.Int()
    email = fields.Email()  # TODO: check how this validates
    email = fields.Email()
    username = fields.Str()
    username = fields.Str()
    name = fields.Str()
    name = fields.Str()
    is_admin = fields.Bool()
    is_admin = fields.Bool()
    submissions = fields.Nested('SubmissionSchema', only=('id', 'note', 'state'), many=True)
    submissions = fields.Nested('SubmissionSchema', only=('id', 'note', 'state'), many=True)
    review_items = fields.Nested('ReviewItemSchema', only=('id',), many=True)  # TODO: update 'only'
    review_items = fields.Nested('ReviewItemSchema', only=('id',), many=True)  # TODO: what to show




class CourseSchema(Schema):
class CourseSchema(Schema):
@@ -123,7 +24,37 @@ class CourseSchema(Schema):
    projects = fields.Nested('ProjectSchema', only=('id', 'name'), many=True)
    projects = fields.Nested('ProjectSchema', only=('id', 'name'), many=True)




class SubmissionFileParamsSchema(Schema):
    source = fields.Nested('SubmissionFileSourceSchema')


class SubmissionFileSourceSchema(Schema):
    type = fields.Str()
    url = fields.Str()  # git@gitlab.fi.muni.cz:foo/project.git
    branch = fields.Str()
    checkout = fields.Str()

    @validates_schema
    def validate_present_fields(self, data):
        if data.get('type') is None:
            raise ValidationError(f"Submission source type is required. Supported values: 'git', 'zip'.")
        if data['type'] not in ('git', 'zip'):
            raise ValidationError(f"Submission source unsupported: {data['type']}. Supported sources: git, zip. ")
        if data['type'] == 'git':
            if any(('url', 'branch', 'checkout')) not in data.keys:
                raise ValidationError(f"File source 'git' requires precisely 'url', 'branch' and 'checkout' attributes.")
        if data['type'] == 'zip':
            if any(('url', 'branch', 'checkout')) in data.keys:
                raise ValidationError(f"File source 'zip' does not require any other attributes.")


class SubmissionCreateSchema(Schema):
    file_params = fields.Nested('SubmissionFileParamsSchema')
    project_params = fields.Dict()


class ProjectSchema(Schema):
class ProjectSchema(Schema):
    # TODO: add state as enum
    id = fields.Str(dump_only=True)
    id = fields.Str(dump_only=True)
    name = fields.Str()
    name = fields.Str()
    config = fields.Nested('ProjectConfigSchema', exclude=('id', '_submissions_allowed_from', '_submissions_allowed_to',
    config = fields.Nested('ProjectConfigSchema', exclude=('id', '_submissions_allowed_from', '_submissions_allowed_to',
@@ -194,13 +125,12 @@ class GroupSchema(Schema):


class SubmissionSchema(Schema):
class SubmissionSchema(Schema):
    id = fields.Str(dump_only=True)
    id = fields.Str(dump_only=True)
    processing_scheduled_for = fields.LocalDateTime()
    parameters = fields.Str()
    state = fields.Str()  # TODO: check this - enum
    note = fields.Str()
    note = fields.Str()
    user = fields.Nested('UserSchema', only=('id', 'uco', 'email', 'username'))
    state = EnumField(SubmissionState)
    project = fields.Nested('ProjectSchema', only=('id', 'name'))
    scheduled_for = fields.LocalDateTime()
    review = fields.Nested('ReviewSchema', only=('id',))  # maybe flatten the review here?
    parameters = fields.Dict()
    project = fields.Nested('ProjectSchema', only=('id', 'name', 'course.id'))  # check dot notation
    user = fields.Str()  # id




class ReviewSchema(Schema):
class ReviewSchema(Schema):
+0 −0

Empty file added.

Loading