Loading portal/database/models.py +6 −2 Original line number Original line Diff line number Diff line Loading @@ -19,7 +19,7 @@ from portal.tools import time def _repr(instance): def _repr(instance): result = f"{instance.__class__.__name__}: " result = f"{instance.__class__.__name__}: " for key, value in vars(instance).items(): for key, value in vars(instance).items(): if not key.startswith("_") and key != 'password_hash': if not key.startswith("_") and key not in ('password_hash', 'review', 'course', 'user', 'project', 'group', 'role', 'review_items'): result += f"{key}={value} " result += f"{key}={value} " return result return result Loading Loading @@ -328,6 +328,9 @@ class SubmissionState(enum.Enum): QUEUED = 3 QUEUED = 3 IN_PROGRESS = 4 IN_PROGRESS = 4 FINISHED = 5 FINISHED = 5 CANCELLED = 6 ABORTED = 7 ARCHIVED = 8 class Submission(db.Model, EntityBase): class Submission(db.Model, EntityBase): Loading Loading @@ -393,11 +396,12 @@ class ReviewItem(db.Model, EntityBase): file = db.Column(db.String(100), nullable=False) file = db.Column(db.String(100), nullable=False) line = db.Column(db.Integer, nullable=False) line = db.Column(db.Integer, nullable=False) def __init__(self, user, review, file, line): def __init__(self, user, review, file, line, content): self.review = review self.review = review self.user = user self.user = user self.file = file self.file = file self.line = line self.line = line self.content = content def __repr__(self): def __repr__(self): return _repr(self) return _repr(self) Loading portal/rest/__init__.py +1 −0 Original line number Original line Diff line number Diff line Loading @@ -186,6 +186,7 @@ submission_create_schema = SubmissionCreateSchema() submission_state_schema = SubmissionSchema(only=('state',)) submission_state_schema = SubmissionSchema(only=('state',)) reviews_schema = ReviewSchema(many=True) reviews_schema = ReviewSchema(many=True) review_schema = ReviewSchema() role_schema = RoleSchema(strict=True) role_schema = RoleSchema(strict=True) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) Loading portal/rest/projects/projects.py +1 −2 Original line number Original line Diff line number Diff line Loading @@ -122,10 +122,9 @@ class ProjectSubmissions(Resource): @jwt_required @jwt_required def post(self, cid, pid): def post(self, cid, pid): log.info(f"POST to {request.url}") log.info(f"POST to {request.url}") # TODO FIX: do not use find_user but autorize_user user = service.find_user(get_jwt_identity()) user = service.find_user(get_jwt_identity()) if not user: if not user: abort(401, message="Invalid access token: user not found.") raise PortalAPIError(401, message="Invalid access token: user not found.") # authorization here # authorization here course = service.find_course(cid) course = service.find_course(cid) Loading portal/rest/submissions/submissions.py +54 −6 Original line number Original line Diff line number Diff line from flask import Blueprint, request from flask import Blueprint, request from flask_jwt_extended import jwt_required, get_jwt_identity from flask_restful import Api, Resource from flask_restful import Api, Resource from portal.rest import submission_schema, submission_state_schema from portal.rest import submission_schema, submission_state_schema, review_schema, reviews_schema from portal.tools.decorators import error_handler from portal.tools.decorators import error_handler from portal.tools.logging import log from portal.tools.logging import log from portal.service import service from portal.service import service Loading @@ -16,9 +17,15 @@ class SubmissionResource(Resource): def get(self, sid): def get(self, sid): log.info(f"GET to {request.url}") log.info(f"GET to {request.url}") submission = service.find_submission(sid) submission = service.find_submission(sid) # TODO: add stuff to the schema, needs testing return submission_schema.dump(submission) return submission_schema.dump(submission) @error_handler def delete(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) service.delete_entity(submission) return '', 204 class SubmissionState(Resource): class SubmissionState(Resource): @error_handler @error_handler Loading @@ -26,7 +33,7 @@ class SubmissionState(Resource): log.info(f"GET to {request.url}") log.info(f"GET to {request.url}") submission = service.find_submission(sid) submission = service.find_submission(sid) # returning the enum value as a string should also be okay # returning the enum value as a string should also be okay return submission_state_schema.dump(submission.state) return submission_state_schema.dump(submission)[0], 200 @error_handler @error_handler def put(self, sid): def put(self, sid): Loading Loading @@ -91,12 +98,51 @@ class SubmissionResubmit(Resource): def post(self, sid): def post(self, sid): log.info(f"POST to {request.url}") log.info(f"POST to {request.url}") source_submission = service.find_submission(sid) source_submission = service.find_submission(sid) json_data = request.get_json() if not json_data: raise PortalAPIError(400, message='No data provided for submission resubmit.') data = submission_schema.load(json_data)[0] # create new submission by copying files from the source submission in storage # create new submission by copying files from the source submission in storage # TODO: add to service new_submission = service.copy_submission(source_submission, note=data['note']) return submission_schema.dump(new_submission), 201 submissions_api.add_resource(SubmissionResource, '/<string:sid>') # needs extension class SubmissionReview(Resource): submissions_api.add_resource(SubmissionState, '/<string:sid>/state') # OK @error_handler def get(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) return review_schema.dump(submission.review)[0], 200 @error_handler @jwt_required def post(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) user = service.find_user(get_jwt_identity()) if not user: raise PortalAPIError(401, message="Invalid access token: user not found.") if not submission.review: service.create_review(submission) json_data = request.get_json() if not json_data: raise PortalAPIError(400, message='No data provided for group creation.') data = review_schema.load(json_data)[0] # also writes to db service.create_review_items(review=submission.review, items=data['review_items'], author=user) log.info(f"Added review items {data['review_items']} to review {submission.review.id} " f"for submission {submission.id}") return review_schema.dump(submission.review)[0], 201 submissions_api.add_resource(SubmissionResource, '/<string:sid>') submissions_api.add_resource(SubmissionState, '/<string:sid>/state') submissions_api.add_resource(SubmissionResult, '/<string:sid>/result') submissions_api.add_resource(SubmissionResult, '/<string:sid>/result') submissions_api.add_resource(SubmissionResubmit, '/<string:sid>/resubmit') submissions_api.add_resource(SubmissionResubmit, '/<string:sid>/resubmit') Loading @@ -106,3 +152,5 @@ submissions_api.add_resource(SubmissionResultFiles, '/<string:sid>/files/results submissions_api.add_resource(SubmissionTestFiles, '/<string:sid>/test_files') submissions_api.add_resource(SubmissionTestFiles, '/<string:sid>/test_files') submissions_api.add_resource(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionReview, '/<string:sid>/review') portal/service/service.py +19 −1 Original line number Original line Diff line number Diff line from portal.service.errors import ResourceNotFoundError from portal.service.errors import ResourceNotFoundError from portal.tools.logging import log from portal.tools.logging import log from portal import db from portal import db from portal.database.models import Course, Role, User, Project, Group, Submission, SubmissionState from portal.database.models import Course, Role, User, Project, Group, Submission, SubmissionState, Review, ReviewItem from portal import storage from portal import storage import copy import copy Loading Loading @@ -208,6 +208,14 @@ def create_submission(user, project, file_params, project_params_string) -> Subm return new_submission return new_submission def copy_submission(source: Submission, note: str): new_submission = Submission(user=source.user, project=source.project, parameters=source.parameters) new_submission.note = note write_entity(new_submission) storage.submissions.clone(source.id, new_submission.id) return new_submission def copy_role(source: Role, target: Course, with_users: str): def copy_role(source: Role, target: Course, with_users: str): new_role = Role(target, source.name) new_role = Role(target, source.name) new_role.set_permissions(**vars(source.permissions)) new_role.set_permissions(**vars(source.permissions)) Loading Loading @@ -255,3 +263,13 @@ def copy_course(source: Course, target: Course, config: dict): def find_users(ids: list) -> list: def find_users(ids: list) -> list: return [find_user(i) for i in ids] return [find_user(i) for i in ids] def create_review_items(review: Review, author: User, items: list): for item in items: new_item = ReviewItem(user=author, review=review, file=item['file'], line=item['line'], content=item['content']) write_entity(review) def create_review(submission: Submission): r = Review(submission=submission) write_entity(r) Loading
portal/database/models.py +6 −2 Original line number Original line Diff line number Diff line Loading @@ -19,7 +19,7 @@ from portal.tools import time def _repr(instance): def _repr(instance): result = f"{instance.__class__.__name__}: " result = f"{instance.__class__.__name__}: " for key, value in vars(instance).items(): for key, value in vars(instance).items(): if not key.startswith("_") and key != 'password_hash': if not key.startswith("_") and key not in ('password_hash', 'review', 'course', 'user', 'project', 'group', 'role', 'review_items'): result += f"{key}={value} " result += f"{key}={value} " return result return result Loading Loading @@ -328,6 +328,9 @@ class SubmissionState(enum.Enum): QUEUED = 3 QUEUED = 3 IN_PROGRESS = 4 IN_PROGRESS = 4 FINISHED = 5 FINISHED = 5 CANCELLED = 6 ABORTED = 7 ARCHIVED = 8 class Submission(db.Model, EntityBase): class Submission(db.Model, EntityBase): Loading Loading @@ -393,11 +396,12 @@ class ReviewItem(db.Model, EntityBase): file = db.Column(db.String(100), nullable=False) file = db.Column(db.String(100), nullable=False) line = db.Column(db.Integer, nullable=False) line = db.Column(db.Integer, nullable=False) def __init__(self, user, review, file, line): def __init__(self, user, review, file, line, content): self.review = review self.review = review self.user = user self.user = user self.file = file self.file = file self.line = line self.line = line self.content = content def __repr__(self): def __repr__(self): return _repr(self) return _repr(self) Loading
portal/rest/__init__.py +1 −0 Original line number Original line Diff line number Diff line Loading @@ -186,6 +186,7 @@ submission_create_schema = SubmissionCreateSchema() submission_state_schema = SubmissionSchema(only=('state',)) submission_state_schema = SubmissionSchema(only=('state',)) reviews_schema = ReviewSchema(many=True) reviews_schema = ReviewSchema(many=True) review_schema = ReviewSchema() role_schema = RoleSchema(strict=True) role_schema = RoleSchema(strict=True) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) Loading
portal/rest/projects/projects.py +1 −2 Original line number Original line Diff line number Diff line Loading @@ -122,10 +122,9 @@ class ProjectSubmissions(Resource): @jwt_required @jwt_required def post(self, cid, pid): def post(self, cid, pid): log.info(f"POST to {request.url}") log.info(f"POST to {request.url}") # TODO FIX: do not use find_user but autorize_user user = service.find_user(get_jwt_identity()) user = service.find_user(get_jwt_identity()) if not user: if not user: abort(401, message="Invalid access token: user not found.") raise PortalAPIError(401, message="Invalid access token: user not found.") # authorization here # authorization here course = service.find_course(cid) course = service.find_course(cid) Loading
portal/rest/submissions/submissions.py +54 −6 Original line number Original line Diff line number Diff line from flask import Blueprint, request from flask import Blueprint, request from flask_jwt_extended import jwt_required, get_jwt_identity from flask_restful import Api, Resource from flask_restful import Api, Resource from portal.rest import submission_schema, submission_state_schema from portal.rest import submission_schema, submission_state_schema, review_schema, reviews_schema from portal.tools.decorators import error_handler from portal.tools.decorators import error_handler from portal.tools.logging import log from portal.tools.logging import log from portal.service import service from portal.service import service Loading @@ -16,9 +17,15 @@ class SubmissionResource(Resource): def get(self, sid): def get(self, sid): log.info(f"GET to {request.url}") log.info(f"GET to {request.url}") submission = service.find_submission(sid) submission = service.find_submission(sid) # TODO: add stuff to the schema, needs testing return submission_schema.dump(submission) return submission_schema.dump(submission) @error_handler def delete(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) service.delete_entity(submission) return '', 204 class SubmissionState(Resource): class SubmissionState(Resource): @error_handler @error_handler Loading @@ -26,7 +33,7 @@ class SubmissionState(Resource): log.info(f"GET to {request.url}") log.info(f"GET to {request.url}") submission = service.find_submission(sid) submission = service.find_submission(sid) # returning the enum value as a string should also be okay # returning the enum value as a string should also be okay return submission_state_schema.dump(submission.state) return submission_state_schema.dump(submission)[0], 200 @error_handler @error_handler def put(self, sid): def put(self, sid): Loading Loading @@ -91,12 +98,51 @@ class SubmissionResubmit(Resource): def post(self, sid): def post(self, sid): log.info(f"POST to {request.url}") log.info(f"POST to {request.url}") source_submission = service.find_submission(sid) source_submission = service.find_submission(sid) json_data = request.get_json() if not json_data: raise PortalAPIError(400, message='No data provided for submission resubmit.') data = submission_schema.load(json_data)[0] # create new submission by copying files from the source submission in storage # create new submission by copying files from the source submission in storage # TODO: add to service new_submission = service.copy_submission(source_submission, note=data['note']) return submission_schema.dump(new_submission), 201 submissions_api.add_resource(SubmissionResource, '/<string:sid>') # needs extension class SubmissionReview(Resource): submissions_api.add_resource(SubmissionState, '/<string:sid>/state') # OK @error_handler def get(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) return review_schema.dump(submission.review)[0], 200 @error_handler @jwt_required def post(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) user = service.find_user(get_jwt_identity()) if not user: raise PortalAPIError(401, message="Invalid access token: user not found.") if not submission.review: service.create_review(submission) json_data = request.get_json() if not json_data: raise PortalAPIError(400, message='No data provided for group creation.') data = review_schema.load(json_data)[0] # also writes to db service.create_review_items(review=submission.review, items=data['review_items'], author=user) log.info(f"Added review items {data['review_items']} to review {submission.review.id} " f"for submission {submission.id}") return review_schema.dump(submission.review)[0], 201 submissions_api.add_resource(SubmissionResource, '/<string:sid>') submissions_api.add_resource(SubmissionState, '/<string:sid>/state') submissions_api.add_resource(SubmissionResult, '/<string:sid>/result') submissions_api.add_resource(SubmissionResult, '/<string:sid>/result') submissions_api.add_resource(SubmissionResubmit, '/<string:sid>/resubmit') submissions_api.add_resource(SubmissionResubmit, '/<string:sid>/resubmit') Loading @@ -106,3 +152,5 @@ submissions_api.add_resource(SubmissionResultFiles, '/<string:sid>/files/results submissions_api.add_resource(SubmissionTestFiles, '/<string:sid>/test_files') submissions_api.add_resource(SubmissionTestFiles, '/<string:sid>/test_files') submissions_api.add_resource(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionReview, '/<string:sid>/review')
portal/service/service.py +19 −1 Original line number Original line Diff line number Diff line from portal.service.errors import ResourceNotFoundError from portal.service.errors import ResourceNotFoundError from portal.tools.logging import log from portal.tools.logging import log from portal import db from portal import db from portal.database.models import Course, Role, User, Project, Group, Submission, SubmissionState from portal.database.models import Course, Role, User, Project, Group, Submission, SubmissionState, Review, ReviewItem from portal import storage from portal import storage import copy import copy Loading Loading @@ -208,6 +208,14 @@ def create_submission(user, project, file_params, project_params_string) -> Subm return new_submission return new_submission def copy_submission(source: Submission, note: str): new_submission = Submission(user=source.user, project=source.project, parameters=source.parameters) new_submission.note = note write_entity(new_submission) storage.submissions.clone(source.id, new_submission.id) return new_submission def copy_role(source: Role, target: Course, with_users: str): def copy_role(source: Role, target: Course, with_users: str): new_role = Role(target, source.name) new_role = Role(target, source.name) new_role.set_permissions(**vars(source.permissions)) new_role.set_permissions(**vars(source.permissions)) Loading Loading @@ -255,3 +263,13 @@ def copy_course(source: Course, target: Course, config: dict): def find_users(ids: list) -> list: def find_users(ids: list) -> list: return [find_user(i) for i in ids] return [find_user(i) for i in ids] def create_review_items(review: Review, author: User, items: list): for item in items: new_item = ReviewItem(user=author, review=review, file=item['file'], line=item['line'], content=item['content']) write_entity(review) def create_review(submission: Submission): r = Review(submission=submission) write_entity(r)