Loading portal/database/models.py +6 −2 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ from portal.tools import time def _repr(instance): result = f"{instance.__class__.__name__}: " 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} " return result Loading Loading @@ -328,6 +328,9 @@ class SubmissionState(enum.Enum): QUEUED = 3 IN_PROGRESS = 4 FINISHED = 5 CANCELLED = 6 ABORTED = 7 ARCHIVED = 8 class Submission(db.Model, EntityBase): Loading Loading @@ -393,11 +396,12 @@ class ReviewItem(db.Model, EntityBase): file = db.Column(db.String(100), 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.user = user self.file = file self.line = line self.content = content def __repr__(self): return _repr(self) Loading portal/rest/__init__.py +1 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,7 @@ submission_create_schema = SubmissionCreateSchema() submission_state_schema = SubmissionSchema(only=('state',)) reviews_schema = ReviewSchema(many=True) review_schema = ReviewSchema() role_schema = RoleSchema(strict=True) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) Loading portal/rest/projects/projects.py +1 −2 Original line number Diff line number Diff line Loading @@ -122,10 +122,9 @@ class ProjectSubmissions(Resource): @jwt_required def post(self, cid, pid): log.info(f"POST to {request.url}") # TODO FIX: do not use find_user but autorize_user user = service.find_user(get_jwt_identity()) if not user: abort(401, message="Invalid access token: user not found.") raise PortalAPIError(401, message="Invalid access token: user not found.") # authorization here course = service.find_course(cid) Loading portal/rest/submissions/submissions.py +54 −6 Original line number Diff line number Diff line from flask import Blueprint, request from flask_jwt_extended import jwt_required, get_jwt_identity 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.logging import log from portal.service import service Loading @@ -16,9 +17,15 @@ class SubmissionResource(Resource): def get(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) # TODO: add stuff to the schema, needs testing 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): @error_handler Loading @@ -26,7 +33,7 @@ class SubmissionState(Resource): log.info(f"GET to {request.url}") submission = service.find_submission(sid) # 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 def put(self, sid): Loading Loading @@ -91,12 +98,51 @@ class SubmissionResubmit(Resource): def post(self, sid): log.info(f"POST to {request.url}") 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 # 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 submissions_api.add_resource(SubmissionState, '/<string:sid>/state') # OK class SubmissionReview(Resource): @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(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(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionReview, '/<string:sid>/review') portal/service/service.py +19 −1 Original line number Diff line number Diff line from portal.service.errors import ResourceNotFoundError from portal.tools.logging import log 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 import copy Loading Loading @@ -208,6 +208,14 @@ def create_submission(user, project, file_params, project_params_string) -> Subm 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): new_role = Role(target, source.name) 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: 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 Diff line number Diff line Loading @@ -19,7 +19,7 @@ from portal.tools import time def _repr(instance): result = f"{instance.__class__.__name__}: " 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} " return result Loading Loading @@ -328,6 +328,9 @@ class SubmissionState(enum.Enum): QUEUED = 3 IN_PROGRESS = 4 FINISHED = 5 CANCELLED = 6 ABORTED = 7 ARCHIVED = 8 class Submission(db.Model, EntityBase): Loading Loading @@ -393,11 +396,12 @@ class ReviewItem(db.Model, EntityBase): file = db.Column(db.String(100), 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.user = user self.file = file self.line = line self.content = content def __repr__(self): return _repr(self) Loading
portal/rest/__init__.py +1 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,7 @@ submission_create_schema = SubmissionCreateSchema() submission_state_schema = SubmissionSchema(only=('state',)) reviews_schema = ReviewSchema(many=True) review_schema = ReviewSchema() role_schema = RoleSchema(strict=True) roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course')) Loading
portal/rest/projects/projects.py +1 −2 Original line number Diff line number Diff line Loading @@ -122,10 +122,9 @@ class ProjectSubmissions(Resource): @jwt_required def post(self, cid, pid): log.info(f"POST to {request.url}") # TODO FIX: do not use find_user but autorize_user user = service.find_user(get_jwt_identity()) if not user: abort(401, message="Invalid access token: user not found.") raise PortalAPIError(401, message="Invalid access token: user not found.") # authorization here course = service.find_course(cid) Loading
portal/rest/submissions/submissions.py +54 −6 Original line number Diff line number Diff line from flask import Blueprint, request from flask_jwt_extended import jwt_required, get_jwt_identity 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.logging import log from portal.service import service Loading @@ -16,9 +17,15 @@ class SubmissionResource(Resource): def get(self, sid): log.info(f"GET to {request.url}") submission = service.find_submission(sid) # TODO: add stuff to the schema, needs testing 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): @error_handler Loading @@ -26,7 +33,7 @@ class SubmissionState(Resource): log.info(f"GET to {request.url}") submission = service.find_submission(sid) # 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 def put(self, sid): Loading Loading @@ -91,12 +98,51 @@ class SubmissionResubmit(Resource): def post(self, sid): log.info(f"POST to {request.url}") 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 # 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 submissions_api.add_resource(SubmissionState, '/<string:sid>/state') # OK class SubmissionReview(Resource): @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(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(SubmissionSources, '/<string:sid>/sources') submissions_api.add_resource(SubmissionReview, '/<string:sid>/review')
portal/service/service.py +19 −1 Original line number Diff line number Diff line from portal.service.errors import ResourceNotFoundError from portal.tools.logging import log 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 import copy Loading Loading @@ -208,6 +208,14 @@ def create_submission(user, project, file_params, project_params_string) -> Subm 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): new_role = Role(target, source.name) 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: 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)